fix: Normalize optional user fields (department, designation, and supplier) in user modals and update AuthenticatedImage formatting.
This commit is contained in:
parent
d242bbf708
commit
c9503c78be
@ -1,9 +1,17 @@
|
||||
import { useState, useEffect, type ImgHTMLAttributes, type ReactElement } from 'react';
|
||||
import { fileService } from '@/services/file-service';
|
||||
import apiClient from '@/services/api-client';
|
||||
import { Loader2, ImageIcon } from 'lucide-react';
|
||||
import {
|
||||
useState,
|
||||
useEffect,
|
||||
type ImgHTMLAttributes,
|
||||
type ReactElement,
|
||||
} from "react";
|
||||
import { fileService } from "@/services/file-service";
|
||||
import apiClient from "@/services/api-client";
|
||||
import { Loader2, ImageIcon } from "lucide-react";
|
||||
|
||||
interface AuthenticatedImageProps extends Omit<ImgHTMLAttributes<HTMLImageElement>, 'src'> {
|
||||
interface AuthenticatedImageProps extends Omit<
|
||||
ImgHTMLAttributes<HTMLImageElement>,
|
||||
"src"
|
||||
> {
|
||||
fileId?: string | null;
|
||||
src?: string | null;
|
||||
fallback?: ReactElement;
|
||||
@ -14,7 +22,7 @@ export const AuthenticatedImage = ({
|
||||
src,
|
||||
fallback,
|
||||
className,
|
||||
alt = 'Image',
|
||||
alt = "Image",
|
||||
...props
|
||||
}: AuthenticatedImageProps): ReactElement => {
|
||||
const [blobUrl, setBlobUrl] = useState<string | null>(null);
|
||||
@ -23,13 +31,15 @@ export const AuthenticatedImage = ({
|
||||
|
||||
useEffect(() => {
|
||||
// If it's already a blob URL (local preview) or a data URL, use it directly
|
||||
if (src && (src.startsWith('blob:') || src.startsWith('data:'))) {
|
||||
if (src && (src.startsWith("blob:") || src.startsWith("data:"))) {
|
||||
setBlobUrl(src);
|
||||
return;
|
||||
}
|
||||
|
||||
const baseUrl = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000';
|
||||
const isBackendUrl = src && src.includes(`${baseUrl}/files/`) && src.includes('/preview');
|
||||
const baseUrl =
|
||||
import.meta.env.VITE_API_BASE_URL || "http://localhost:3000";
|
||||
const isBackendUrl =
|
||||
src && src.includes(`${baseUrl}/files/`) && src.includes("/preview");
|
||||
|
||||
// If we have a fileId or a backend URL, fetch it via authenticated request
|
||||
if (fileId || isBackendUrl) {
|
||||
@ -42,7 +52,7 @@ export const AuthenticatedImage = ({
|
||||
if (fileId) {
|
||||
url = await fileService.getPreview(fileId);
|
||||
} else if (src) {
|
||||
const response = await apiClient.get(src, { responseType: 'blob' });
|
||||
const response = await apiClient.get(src, { responseType: "blob" });
|
||||
url = URL.createObjectURL(response.data);
|
||||
} else {
|
||||
return;
|
||||
@ -52,7 +62,7 @@ export const AuthenticatedImage = ({
|
||||
setBlobUrl(url);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch authenticated image:', err);
|
||||
console.error("Failed to fetch authenticated image:", err);
|
||||
if (isMounted) {
|
||||
setError(true);
|
||||
}
|
||||
@ -67,7 +77,7 @@ export const AuthenticatedImage = ({
|
||||
|
||||
return () => {
|
||||
isMounted = false;
|
||||
if (blobUrl && blobUrl.startsWith('blob:')) {
|
||||
if (blobUrl && blobUrl.startsWith("blob:")) {
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
}
|
||||
};
|
||||
@ -79,23 +89,29 @@ export const AuthenticatedImage = ({
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className={`flex items-center justify-center bg-[#f5f7fa] rounded-md ${className}`}>
|
||||
<div
|
||||
className={`flex items-center justify-center bg-[#f5f7fa] rounded-md ${className}`}
|
||||
>
|
||||
<Loader2 className="w-5 h-5 text-[#6b7280] animate-spin" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error || (!blobUrl && !src)) {
|
||||
return fallback || (
|
||||
<div className={`flex items-center justify-center bg-[#f5f7fa] rounded-md ${className}`}>
|
||||
return (
|
||||
fallback || (
|
||||
<div
|
||||
className={`flex items-center justify-center bg-[#f5f7fa] rounded-md ${className}`}
|
||||
>
|
||||
<ImageIcon className="w-5 h-5 text-[#9ca3af]" />
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<img
|
||||
src={blobUrl || src || ''}
|
||||
src={blobUrl || src || ""}
|
||||
className={className}
|
||||
alt={alt}
|
||||
{...props}
|
||||
|
||||
@ -345,6 +345,17 @@ export const EditUserModal = ({
|
||||
if (defaultTenantId) {
|
||||
data.tenant_id = defaultTenantId;
|
||||
}
|
||||
// Normalize empty strings to null for optional UUID fields
|
||||
if (!data.department_id) data.department_id = undefined;
|
||||
if (!data.designation_id) data.designation_id = undefined;
|
||||
|
||||
// If category is tenant_user, supplier_id should always be null
|
||||
if (data.category === "tenant_user") {
|
||||
data.supplier_id = null;
|
||||
} else if (!data.supplier_id) {
|
||||
data.supplier_id = null;
|
||||
}
|
||||
|
||||
await onSubmit(userId, data);
|
||||
} catch (error: any) {
|
||||
if (
|
||||
@ -491,13 +502,13 @@ export const EditUserModal = ({
|
||||
{ value: "supplier_user", label: "Supplier User" },
|
||||
]}
|
||||
value={categoryValue || "tenant_user"}
|
||||
onValueChange={(value) =>
|
||||
setValue(
|
||||
"category",
|
||||
value as "tenant_user" | "supplier_user",
|
||||
{ shouldValidate: true },
|
||||
)
|
||||
onValueChange={(value) => {
|
||||
const category = value as "tenant_user" | "supplier_user";
|
||||
setValue("category", category, { shouldValidate: true });
|
||||
if (category === "tenant_user") {
|
||||
setValue("supplier_id", null);
|
||||
}
|
||||
}}
|
||||
error={errors.category?.message}
|
||||
/>
|
||||
|
||||
|
||||
@ -216,6 +216,17 @@ export const NewUserModal = ({
|
||||
clearErrors();
|
||||
try {
|
||||
const { confirmPassword, ...submitData } = data;
|
||||
// Normalize empty strings to null for optional UUID fields
|
||||
if (!submitData.department_id) submitData.department_id = undefined;
|
||||
if (!submitData.designation_id) submitData.designation_id = undefined;
|
||||
|
||||
// If category is tenant_user, supplier_id should always be null
|
||||
if (submitData.category === "tenant_user") {
|
||||
submitData.supplier_id = null;
|
||||
} else if (!submitData.supplier_id) {
|
||||
submitData.supplier_id = null;
|
||||
}
|
||||
|
||||
await onSubmit(submitData);
|
||||
} catch (error: any) {
|
||||
// Handle validation errors from API
|
||||
@ -387,11 +398,13 @@ export const NewUserModal = ({
|
||||
{ value: "supplier_user", label: "Supplier User" },
|
||||
]}
|
||||
value={categoryValue || "tenant_user"}
|
||||
onValueChange={(value) =>
|
||||
setValue("category", value as "tenant_user" | "supplier_user", {
|
||||
shouldValidate: true,
|
||||
})
|
||||
onValueChange={(value) => {
|
||||
const category = value as "tenant_user" | "supplier_user";
|
||||
setValue("category", category, { shouldValidate: true });
|
||||
if (category === "tenant_user") {
|
||||
setValue("supplier_id", null);
|
||||
}
|
||||
}}
|
||||
error={errors.category?.message}
|
||||
/>
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user