Qassure-frontend/src/components/shared/EditTenantModal.tsx
2026-01-19 19:36:31 +05:30

203 lines
6.0 KiB
TypeScript

import { useEffect, useState } from 'react';
import type { ReactElement } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { Loader2 } from 'lucide-react';
import { Modal, FormField, FormSelect, PrimaryButton, SecondaryButton } from '@/components/shared';
import type { Tenant } from '@/types/tenant';
// Validation schema
const editTenantSchema = z.object({
name: z.string().min(1, 'Tenant name is required'),
slug: z.string().min(1, 'Slug is required'),
status: z.enum(['active', 'suspended', 'blocked'], {
message: 'Status is required',
}),
timezone: z.string().min(1, 'Timezone is required'),
});
type EditTenantFormData = z.infer<typeof editTenantSchema>;
interface EditTenantModalProps {
isOpen: boolean;
onClose: () => void;
tenantId: string | null;
onLoadTenant: (id: string) => Promise<Tenant>;
onSubmit: (id: string, data: EditTenantFormData) => Promise<void>;
isLoading?: boolean;
}
const statusOptions = [
{ value: 'active', label: 'Active' },
{ value: 'suspended', label: 'Suspended' },
{ value: 'blocked', label: 'Blocked' },
];
const timezoneOptions = [
{ value: 'America/New_York', label: 'America/New_York (EST)' },
{ value: 'America/Chicago', label: 'America/Chicago (CST)' },
{ value: 'America/Denver', label: 'America/Denver (MST)' },
{ value: 'America/Los_Angeles', label: 'America/Los_Angeles (PST)' },
{ value: 'UTC', label: 'UTC' },
{ value: 'Europe/London', label: 'Europe/London (GMT)' },
{ value: 'Asia/Dubai', label: 'Asia/Dubai (GST)' },
{ value: 'Asia/Kolkata', label: 'Asia/Kolkata (IST)' },
{ value: 'Asia/Tokyo', label: 'Asia/Tokyo (JST)' },
{ value: 'Australia/Sydney', label: 'Australia/Sydney (AEDT)' },
];
export const EditTenantModal = ({
isOpen,
onClose,
tenantId,
onLoadTenant,
onSubmit,
isLoading = false,
}: EditTenantModalProps): ReactElement | null => {
const [isLoadingTenant, setIsLoadingTenant] = useState<boolean>(false);
const [loadError, setLoadError] = useState<string | null>(null);
const {
register,
handleSubmit,
setValue,
watch,
reset,
formState: { errors },
} = useForm<EditTenantFormData>({
resolver: zodResolver(editTenantSchema),
});
const statusValue = watch('status');
const timezoneValue = watch('timezone');
// Load tenant data when modal opens
useEffect(() => {
if (isOpen && tenantId) {
const loadTenant = async (): Promise<void> => {
try {
setIsLoadingTenant(true);
setLoadError(null);
const tenant = await onLoadTenant(tenantId);
reset({
name: tenant.name,
slug: tenant.slug,
status: tenant.status,
timezone: tenant.settings?.timezone || 'America/New_York',
});
} catch (err: any) {
setLoadError(err?.response?.data?.error?.message || 'Failed to load tenant details');
} finally {
setIsLoadingTenant(false);
}
};
loadTenant();
} else {
reset({
name: '',
slug: '',
status: 'active',
timezone: 'America/New_York',
});
setLoadError(null);
}
}, [isOpen, tenantId, onLoadTenant, reset]);
const handleFormSubmit = async (data: EditTenantFormData): Promise<void> => {
if (tenantId) {
await onSubmit(tenantId, data);
}
};
return (
<Modal
isOpen={isOpen}
onClose={onClose}
title="Edit Tenant"
description="Update tenant information"
maxWidth="md"
footer={
<>
<SecondaryButton
type="button"
onClick={onClose}
disabled={isLoading || isLoadingTenant}
className="px-4 py-2.5 text-sm"
>
Cancel
</SecondaryButton>
<PrimaryButton
type="button"
onClick={handleSubmit(handleFormSubmit)}
disabled={isLoading || isLoadingTenant}
size="default"
className="px-4 py-2.5 text-sm"
>
{isLoading ? 'Updating...' : 'Update Tenant'}
</PrimaryButton>
</>
}
>
<form onSubmit={handleSubmit(handleFormSubmit)} className="p-5">
{isLoadingTenant && (
<div className="flex items-center justify-center py-12">
<Loader2 className="w-6 h-6 text-[#112868] animate-spin" />
</div>
)}
{loadError && (
<div className="p-4 bg-[rgba(239,68,68,0.1)] border border-[#ef4444] rounded-md mb-4">
<p className="text-sm text-[#ef4444]">{loadError}</p>
</div>
)}
{!isLoadingTenant && (
<div className="flex flex-col gap-0">
{/* Tenant Name */}
<FormField
label="Tenant Name"
required
placeholder="Enter tenant name"
error={errors.name?.message}
{...register('name')}
/>
{/* Slug */}
<FormField
label="Slug"
required
placeholder="Enter slug name"
error={errors.slug?.message}
{...register('slug')}
/>
{/* Status and Timezone Row */}
<div className="grid grid-cols-2 gap-5 pb-4">
<FormSelect
label="Status"
required
placeholder="Select Status"
options={statusOptions}
value={statusValue}
onValueChange={(value) => setValue('status', value as 'active' | 'suspended' | 'blocked')}
error={errors.status?.message}
/>
<FormSelect
label="Timezone"
required
placeholder="Select Timezone"
options={timezoneOptions}
value={timezoneValue}
onValueChange={(value) => setValue('timezone', value)}
error={errors.timezone?.message}
/>
</div>
</div>
)}
</form>
</Modal>
);
};