"use client" import { AlertTriangle, ImageIcon, Plus, Save, UploadIcon } from "lucide-react" import { useRef, useState } from "react" import { Button } from "@/shared/components/ui/button" import { Alert, AlertTitle } from "@/shared/components/ui/alert" import { FieldGroup } from "@/shared/components/ui/field" import { Avatar, AvatarFallback, AvatarImage } from "@/shared/components/ui/avatar" import { Separator } from "@/shared/components/ui/separator" import { Rhform, RhfTextField, RhfSelectField, RhfAsyncSelectField, RhfCheckboxField, RhfTextareaField, } from "@/shared/components/form" import { toast } from "sonner" import { useAuthApi } from "@/shared/useApi" import { useResourceForm } from "@/shared/hooks/use-resource-form" import { useFormMutation } from "@/shared/hooks/use-form-mutation" import { toRelation, toId } from "@/shared/lib/utils" import { employeeFormSchema, type EmployeeFormValues, STATUS_OPTIONS, TYPE_OPTIONS, WAGE_TYPE_OPTIONS, GENDER_OPTIONS, MARITAL_OPTIONS, } from "./employee.schema" import { EMPLOYEE_ROUTES, DEPARTMENT_ROUTES, SHOP_TIMING_ROUTES, SHOP_CALENDAR_ROUTES, GEO_ROUTES } from "@garage/api" const titleCase = (v: string) => v.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()) const STATUS_SELECT_OPTIONS = STATUS_OPTIONS.map((v) => ({ value: v, label: titleCase(v) })) const TYPE_SELECT_OPTIONS = TYPE_OPTIONS.map((v) => ({ value: v, label: titleCase(v) })) const WAGE_TYPE_SELECT_OPTIONS = WAGE_TYPE_OPTIONS.map((v) => ({ value: v, label: v })) const GENDER_SELECT_OPTIONS = GENDER_OPTIONS.map((v) => ({ value: v, label: titleCase(v) })) const MARITAL_SELECT_OPTIONS = MARITAL_OPTIONS.map((v) => ({ value: v, label: titleCase(v) })) export type EmployeeFormProps = { resourceId?: string | null initialData?: unknown onSuccess?: () => void } const DEFAULT_VALUES: EmployeeFormValues = { department: null, country: null, shop_calender: null, shop_timing: null, role_id: null, first_name: "", last_name: "", email: "", password: "", phone: "", designation: "", salary: null, wage_type: null, status: "active", type: "employee", track_attendance: true, notify_owner_when_punch_in_out: false, geo_fence_radius: null, address: "", date_of_birth: "", gender: null, marital_status: null, national_id: "", hire_date: "", emergency_contact_name: "", emergency_contact_phone: "", bank_name: "", bank_account_number: "", bank_iban: "", hourly_rate: null, commission_rate: null, } function mapToFormValues(data: unknown): EmployeeFormValues { const d = (data as any)?.data ?? data ?? {} const formatDate = (v: unknown): string => { if (!v) return "" const s = String(v) return s.length >= 10 ? s.slice(0, 10) : s } return { department: toRelation(d.department_id, d.department?.name), country: toRelation(d.country_id, d.country?.name), shop_calender: toRelation(d.shop_calender_id, d.shop_calender?.title), shop_timing: toRelation(d.shop_timing_id, d.shop_timing?.title), role_id: d.role_id ? Number(d.role_id) : null, first_name: d.first_name || "", last_name: d.last_name || "", email: d.email || "", password: "", phone: d.phone || "", designation: d.designation || "", salary: d.salary ?? null, wage_type: d.wage_type ?? null, status: d.status || "active", type: d.type || "employee", track_attendance: d.track_attendance ?? true, notify_owner_when_punch_in_out: d.notify_owner_when_punch_in_out ?? false, geo_fence_radius: d.geo_fence_radius ?? null, address: d.address || "", date_of_birth: formatDate(d.date_of_birth), gender: d.gender ?? null, marital_status: d.marital_status ?? null, national_id: d.national_id || "", hire_date: formatDate(d.hire_date), emergency_contact_name: d.emergency_contact_name || "", emergency_contact_phone: d.emergency_contact_phone || "", bank_name: d.bank_name || "", bank_account_number: d.bank_account_number || "", bank_iban: d.bank_iban || "", hourly_rate: d.hourly_rate ?? null, commission_rate: d.commission_rate ?? null, } } function mapFormToPayload(values: EmployeeFormValues) { return { department_id: toId(values.department), country_id: toId(values.country), shop_calender_id: toId(values.shop_calender), shop_timing_id: toId(values.shop_timing), role_id: values.role_id ?? undefined, first_name: values.first_name, last_name: values.last_name, email: values.email, password: values.password || undefined, phone: values.phone || undefined, designation: values.designation || undefined, salary: values.salary ?? undefined, wage_type: values.wage_type ?? undefined, status: values.status, type: values.type, track_attendance: values.track_attendance, notify_owner_when_punch_in_out: values.notify_owner_when_punch_in_out, geo_fence_radius: values.geo_fence_radius ?? undefined, address: values.address || undefined, date_of_birth: values.date_of_birth || undefined, gender: values.gender ?? undefined, marital_status: values.marital_status ?? undefined, national_id: values.national_id || undefined, hire_date: values.hire_date || undefined, emergency_contact_name: values.emergency_contact_name || undefined, emergency_contact_phone: values.emergency_contact_phone || undefined, bank_name: values.bank_name || undefined, bank_account_number: values.bank_account_number || undefined, bank_iban: values.bank_iban || undefined, hourly_rate: values.hourly_rate ?? undefined, commission_rate: values.commission_rate ?? undefined, } } const mapLookupOption = (item: any) => ({ value: String(item.id), label: item.name ?? item.title ?? String(item.id), }) const STORE_OBJECT = { getOptionValue: (o: any) => o, getOptionLabel: (o: any) => o.label } function SectionHeading({ title, description }: { title: string; description?: string }) { return (

{title}

{description &&

{description}

}
) } function AvatarUpload({ employeeId, initialUrl }: { employeeId: string; initialUrl?: string | null }) { const api = useAuthApi() const inputRef = useRef(null) const [previewUrl, setPreviewUrl] = useState(initialUrl ?? null) const [uploading, setUploading] = useState(false) async function handleFileChange(e: React.ChangeEvent) { const file = e.target.files?.[0] if (!file) return setUploading(true) try { const res: any = await api.employees.uploadAvatar(employeeId, file) const url = res?.data?.avatar_url ?? URL.createObjectURL(file) setPreviewUrl(url) toast.success("Avatar updated") } catch (err: any) { toast.error(err?.message ?? "Failed to upload avatar") } finally { setUploading(false) e.target.value = "" } } return (
{previewUrl ? : null}

JPG, PNG, WEBP. Max 5 MB.

) } export function EmployeeForm({ resourceId, initialData, onSuccess }: EmployeeFormProps) { const api = useAuthApi() const { form, isEditing } = useResourceForm({ schema: employeeFormSchema, defaultValues: DEFAULT_VALUES, resourceId, initialData, initialize: (id) => api.employees.show(id), queryKey: [EMPLOYEE_ROUTES.BY_ID, resourceId], mapToFormValues: mapToFormValues, }) const { mutate, error, isPending } = useFormMutation(form, { mutationFn: (values: EmployeeFormValues) => { const payload = mapFormToPayload(values) const promise = isEditing && resourceId ? api.employees.update(resourceId, payload) : api.employees.create(payload) toast.promise(promise, { loading: isEditing ? "Updating employee..." : "Creating employee...", success: isEditing ? "Employee updated successfully" : "Employee created successfully", error: isEditing ? "Failed to update employee" : "Failed to create employee", }) return promise }, onSuccess: () => { form.reset() onSuccess?.() }, }) const initialAvatarUrl = (initialData as any)?.avatar_url ?? ((initialData as any)?.avatar_path ? `/storage/${(initialData as any).avatar_path}` : null) return ( mutate(values)}> {error && ( {isEditing ? "Failed to update employee" : "Failed to create employee"} {error.message} )} {/* Personal */} {isEditing && resourceId ? ( ) : null}
{/* Contact */}
api.geo.countries()} mapOption={mapLookupOption} {...STORE_OBJECT} />
{/* Employment */}
api.departments.list()} mapOption={mapLookupOption} {...STORE_OBJECT} />
{/* Compensation */}
{/* Bank */}
{/* Tracking */}
api.shopCalendars.list()} mapOption={mapLookupOption} {...STORE_OBJECT} /> api.shopTimings.list()} mapOption={mapLookupOption} {...STORE_OBJECT} />
) }