fix: Normalize optional user fields (department, designation, and supplier) in user modals and update AuthenticatedImage formatting.

This commit is contained in:
Yashwin 2026-03-17 10:56:28 +05:30
parent d242bbf708
commit c9503c78be
3 changed files with 140 additions and 100 deletions

View File

@ -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}

View File

@ -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}
/>

View File

@ -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}
/>