221 lines
7.6 KiB
TypeScript
221 lines
7.6 KiB
TypeScript
import { useForm } from 'react-hook-form';
|
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
import { z } from 'zod';
|
|
import { Trash2 } from 'lucide-react';
|
|
import type { ReqCreateSupplier as SupplierCreate } from '@/api/generated/model/reqCreateSupplier';
|
|
import type { ReqUpdateSupplier as SupplierUpdate } from '@/api/generated/model/reqUpdateSupplier';
|
|
import { showToast } from '@/lib/notify';
|
|
import { Typography } from '@/components/ui/typography';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Sheet } from '@/components/ui/sheet';
|
|
import { type Partner } from '../types';
|
|
|
|
const schema = z.object({
|
|
name: z.string().trim().min(1, '회사명/협력사명을 작성해 주십시오.'),
|
|
code: z.string().trim().min(1, '협력사코드를 입력해 주십시오.'),
|
|
managerName: z.string().trim().min(1, '담당자명을 입력해 주십시오.'),
|
|
managerEmail: z.string().trim().email('정확한 담당자 이메일 형식을 점검해 주십시오.'),
|
|
managerPhone: z.string().trim().min(1, '담당자 연락처를 입력해 주십시오.'),
|
|
totalRevenue: z.string().trim().optional(), // 총매출액(원)
|
|
});
|
|
|
|
type FormValues = z.infer<typeof schema>;
|
|
|
|
type PartnerFormSheetProps = {
|
|
open: boolean;
|
|
mode: 'create' | 'edit';
|
|
partner: Partner | null; // edit 모드일 때 초기값 출처
|
|
onCreate: (data: SupplierCreate) => Promise<void>;
|
|
onUpdate: (supplierId: string, data: SupplierUpdate) => Promise<void>;
|
|
onDelete: (supplierId: string, name: string) => void;
|
|
onClose: () => void;
|
|
};
|
|
|
|
// mode/partner로 폼 초기값 산출. create는 기본값, edit는 partner에서.
|
|
function buildDefaults(mode: 'create' | 'edit', partner: Partner | null): FormValues {
|
|
if (mode === 'edit' && partner) {
|
|
return {
|
|
name: partner.name ?? '',
|
|
code: partner.code || '',
|
|
managerName: partner.manager_name || '',
|
|
managerEmail: partner.manager_email || '',
|
|
managerPhone: partner.manager_contact_number || '',
|
|
totalRevenue: partner.total_revenue != null ? String(partner.total_revenue) : '',
|
|
};
|
|
}
|
|
return {
|
|
name: '',
|
|
code: `PART-CORP-${Math.floor(100 + Math.random() * 900)}`,
|
|
managerName: '',
|
|
managerEmail: '',
|
|
managerPhone: '010-',
|
|
totalRevenue: '',
|
|
};
|
|
}
|
|
|
|
const inputClass = 'text-foreground text-xs';
|
|
|
|
export function PartnerFormSheet({
|
|
open,
|
|
mode,
|
|
partner,
|
|
onCreate,
|
|
onUpdate,
|
|
onDelete,
|
|
onClose,
|
|
}: PartnerFormSheetProps) {
|
|
const {
|
|
register,
|
|
handleSubmit,
|
|
formState: { errors, isSubmitting },
|
|
} = useForm<FormValues>({
|
|
resolver: zodResolver(schema),
|
|
defaultValues: buildDefaults(mode, partner),
|
|
});
|
|
|
|
const onValid = async (v: FormValues) => {
|
|
const common = {
|
|
name: v.name,
|
|
code: v.code,
|
|
manager_name: v.managerName,
|
|
manager_email: v.managerEmail,
|
|
manager_contact_number: v.managerPhone,
|
|
total_revenue: v.totalRevenue?.trim() ? Number(v.totalRevenue.replace(/[^0-9]/g, '')) : undefined,
|
|
};
|
|
|
|
if (mode === 'create') {
|
|
try {
|
|
await onCreate(common as SupplierCreate);
|
|
showToast('신규 협력사가 등록되었습니다.', 'success');
|
|
onClose();
|
|
} catch (err) {
|
|
showToast(err instanceof Error ? err.message : '협력사 등록 실패', 'error');
|
|
}
|
|
} else if (partner) {
|
|
try {
|
|
await onUpdate(partner.supplier_id, common as SupplierUpdate);
|
|
showToast('협력사 정보가 최신화되었습니다.', 'success');
|
|
onClose();
|
|
} catch (err) {
|
|
showToast(err instanceof Error ? err.message : '협력사 수정 실패', 'error');
|
|
}
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Sheet
|
|
open={open}
|
|
title={mode === 'create' ? '신규 협력사 등록' : '협력사 정보 관리'}
|
|
onClose={onClose}
|
|
>
|
|
<form onSubmit={handleSubmit(onValid)} className="mt-6 space-y-4 text-xs font-mono">
|
|
{/* Partner Name */}
|
|
<div className="space-y-1">
|
|
<Typography as="label" variant="label">협력사명</Typography>
|
|
<Input
|
|
id="form-partner-name"
|
|
type="text"
|
|
{...register('name')}
|
|
className={inputClass}
|
|
placeholder="예: (주)대현정밀부품"
|
|
/>
|
|
{errors.name && <p className="text-[10px] text-rose-500">{errors.name.message}</p>}
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
{/* Code */}
|
|
<div className="space-y-1">
|
|
<Typography as="label" variant="label">협력사코드</Typography>
|
|
<Input
|
|
id="form-partner-code"
|
|
type="text"
|
|
{...register('code')}
|
|
className={inputClass}
|
|
placeholder="PART-XXXX"
|
|
/>
|
|
{errors.code && <p className="text-[10px] text-rose-500">{errors.code.message}</p>}
|
|
</div>
|
|
{/* 총매출액 */}
|
|
<div className="space-y-1">
|
|
<Typography as="label" variant="label">총매출액 (원, 선택)</Typography>
|
|
<Input
|
|
id="form-partner-revenue"
|
|
type="number"
|
|
min={0}
|
|
{...register('totalRevenue')}
|
|
className={inputClass}
|
|
placeholder="예: 5000000000"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Manager Name */}
|
|
<div className="space-y-1">
|
|
<Typography as="label" variant="label">담당자명</Typography>
|
|
<Input
|
|
id="form-partner-manager"
|
|
type="text"
|
|
{...register('managerName')}
|
|
className={inputClass}
|
|
placeholder="김민우 책임과장"
|
|
/>
|
|
{errors.managerName && <p className="text-[10px] text-rose-500">{errors.managerName.message}</p>}
|
|
</div>
|
|
|
|
{/* Manager Email */}
|
|
<div className="space-y-1">
|
|
<Typography as="label" variant="label">담당자 이메일</Typography>
|
|
<Input
|
|
id="form-partner-email"
|
|
type="email"
|
|
{...register('managerEmail')}
|
|
className={inputClass}
|
|
placeholder="manager@company.co.kr"
|
|
/>
|
|
{errors.managerEmail && <p className="text-[10px] text-rose-500">{errors.managerEmail.message}</p>}
|
|
</div>
|
|
|
|
{/* Manager Phone */}
|
|
<div className="space-y-1">
|
|
<Typography as="label" variant="label">담당자 연락처</Typography>
|
|
<Input
|
|
id="form-partner-phone"
|
|
type="text"
|
|
{...register('managerPhone')}
|
|
className={inputClass}
|
|
placeholder="010-XXXX-XXXX"
|
|
/>
|
|
{errors.managerPhone && <p className="text-[10px] text-rose-500">{errors.managerPhone.message}</p>}
|
|
</div>
|
|
|
|
{/* Buttons wrapper */}
|
|
<div className="pt-4 flex items-center gap-2 border-t border-border mt-8 justify-between">
|
|
{mode === 'edit' && partner && (
|
|
<Button
|
|
type="button"
|
|
variant="destructive"
|
|
size="sm"
|
|
onClick={() => {
|
|
onDelete(partner.supplier_id, partner.name);
|
|
onClose();
|
|
}}
|
|
>
|
|
<Trash2 />
|
|
삭제
|
|
</Button>
|
|
)}
|
|
<div className="flex items-center gap-2 flex-1 justify-end">
|
|
<Button type="button" variant="outline" size="sm" onClick={onClose}>
|
|
취소
|
|
</Button>
|
|
<Button type="submit" size="sm" disabled={isSubmitting}>
|
|
{mode === 'create' ? '신규 파트너 등록' : '변경사항 저장'}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</Sheet>
|
|
);
|
|
}
|