diff options
Diffstat (limited to 'lib/rfqs/table')
| -rw-r--r-- | lib/rfqs/table/ItemsDialog.tsx | 112 | ||||
| -rw-r--r-- | lib/rfqs/table/add-rfq-dialog.tsx | 6 |
2 files changed, 65 insertions, 53 deletions
diff --git a/lib/rfqs/table/ItemsDialog.tsx b/lib/rfqs/table/ItemsDialog.tsx index f1dbf90e..3d822499 100644 --- a/lib/rfqs/table/ItemsDialog.tsx +++ b/lib/rfqs/table/ItemsDialog.tsx @@ -96,16 +96,16 @@ export function RfqsItemsDialog({ rfqType }: RfqsItemsDialogProps) { const rfqId = rfq?.rfqId ?? 0; - + // 편집 가능 여부 확인 - DRAFT 상태일 때만 편집 가능 const isEditable = rfq?.status === "DRAFT"; - + // 초기 아이템 ID 목록을 추적하기 위한 상태 추가 const [initialItemIds, setInitialItemIds] = React.useState<(number | undefined)[]>([]); - + // 삭제된 아이템 ID를 저장하는 상태 추가 const [deletedItemIds, setDeletedItemIds] = React.useState<number[]>([]); - + // 1) form const form = useForm<ItemsFormSchema>({ resolver: zodResolver(itemsFormSchema), @@ -125,24 +125,24 @@ export function RfqsItemsDialog({ // 다이얼로그가 열릴 때마다 폼 초기화 및 초기 아이템 ID 저장 React.useEffect(() => { if (open) { - const initialItems = defaultItems.length > 0 + const initialItems = defaultItems.length > 0 ? defaultItems.map((it) => ({ - id: it.id, - quantity: it.quantity ?? 1, - uom: it.uom ?? "each", - itemCode: it.itemCode ?? "", - description: it.description ?? "", - })) + id: it.id, + quantity: it.quantity ?? 1, + uom: it.uom ?? "each", + itemCode: it.itemCode ?? "", + description: it.description ?? "", + })) : [{ itemCode: "", description: "", quantity: 1, uom: "each" }]; - + form.reset({ rfqId, items: initialItems, }); - + // 초기 아이템 ID 목록 저장 setInitialItemIds(defaultItems.map(item => item.id)); - + // 삭제된 아이템 목록 초기화 setDeletedItemIds([]); setHasUnsavedChanges(false); @@ -158,7 +158,7 @@ export function RfqsItemsDialog({ // 폼 변경 감지 - 편집 가능한 경우에만 변경 감지 React.useEffect(() => { if (!isEditable) return; - + const subscription = form.watch(() => { setHasUnsavedChanges(true); }); @@ -177,16 +177,16 @@ export function RfqsItemsDialog({ // 4) Add item row with auto-focus function handleAddItem() { if (!isEditable) return; - + // 명시적으로 숫자 타입으로 지정 - append({ - itemCode: "", - description: "", - quantity: 1, - uom: "each" + append({ + itemCode: "", + description: "", + quantity: 1, + uom: "each" }); setHasUnsavedChanges(true); - + // 다음 렌더링 사이클에서 새로 추가된 항목에 포커스 setTimeout(() => { const newIndex = fields.length; @@ -200,17 +200,17 @@ export function RfqsItemsDialog({ // 항목 직접 삭제 - 기존 ID가 있을 경우 삭제 목록에 추가 const handleRemoveItem = (index: number) => { if (!isEditable) return; - + const itemToRemove = form.getValues().items[index]; - + // 기존 ID가 있는 아이템이라면 삭제 목록에 추가 if (itemToRemove.id !== undefined) { setDeletedItemIds(prev => [...prev, itemToRemove.id as number]); } - + remove(index); setHasUnsavedChanges(true); - + // 포커스 처리: 다음 항목이 있으면 다음 항목으로, 없으면 마지막 항목으로 setTimeout(() => { const nextIndex = Math.min(index, fields.length - 1); @@ -232,7 +232,7 @@ export function RfqsItemsDialog({ // 필드 포커스 유틸리티 함수 const focusField = (selector: string) => { if (!isEditable) return; - + setTimeout(() => { const element = document.querySelector(selector) as HTMLInputElement | null; if (element) { @@ -244,28 +244,28 @@ export function RfqsItemsDialog({ // 5) Submit - 업데이트된 제출 로직 (생성/수정 + 삭제 처리) async function onSubmit(data: ItemsFormSchema) { if (!isEditable) return; - + try { setIsSubmitting(true); - + // 각 아이템이 유효한지 확인 const anyInvalidItems = data.items.some(item => !item.itemCode || item.quantity < 1); - + if (anyInvalidItems) { toast.error("유효하지 않은 아이템이 있습니다. 모든 필드를 확인해주세요."); setIsSubmitting(false); return; } - + // 1. 삭제 처리 - 삭제된 아이템 ID가 있으면 삭제 요청 - const deletePromises = deletedItemIds.map(id => + const deletePromises = deletedItemIds.map(id => deleteRfqItem({ id: id, rfqId: rfqId, rfqType: rfqType ?? RfqType.PURCHASE }) ); - + // 2. 생성/수정 처리 - 폼에 남아있는 아이템들 const upsertPromises = data.items.map((item) => createRfqItem({ @@ -273,13 +273,13 @@ export function RfqsItemsDialog({ itemCode: item.itemCode, description: item.description, // 명시적으로 숫자로 변환 - quantity: Number(item.quantity), + quantity: Number(item.quantity), uom: item.uom, rfqType: rfqType ?? RfqType.PURCHASE, id: item.id // 기존 ID가 있으면 업데이트, 없으면 생성 }) ); - + // 모든 요청 병렬 처리 await Promise.all([...deletePromises, ...upsertPromises]); @@ -296,7 +296,7 @@ export function RfqsItemsDialog({ // 단축키 처리 - 편집 가능한 경우에만 단축키 활성화 React.useEffect(() => { if (!isEditable) return; - + const handleKeyDown = (e: KeyboardEvent) => { // Alt+N: 새 항목 추가 if (e.altKey && e.key === 'n') { @@ -336,8 +336,8 @@ export function RfqsItemsDialog({ </Badge> )} {rfq?.status && ( - <Badge - variant={rfq.status === "DRAFT" ? "outline" : "secondary"} + <Badge + variant={rfq.status === "DRAFT" ? "outline" : "secondary"} className="ml-1" > {rfq.status} @@ -345,8 +345,8 @@ export function RfqsItemsDialog({ )} </DialogTitle> <DialogDescription> - {isEditable - ? (rfq?.description || '아이템을 각 행에 하나씩 추가할 수 있습니다.') + {isEditable + ? (rfq?.description || '아이템을 각 행에 하나씩 추가할 수 있습니다.') : '드래프트 상태가 아닌 RFQ는 아이템을 편집할 수 없습니다.'} </DialogDescription> </DialogHeader> @@ -393,6 +393,7 @@ export function RfqsItemsDialog({ <div key={field.id} className="flex items-center gap-2 group hover:bg-gray-50 p-1 rounded-md transition-colors"> {/* -- itemCode + Popover(Select) -- */} {isEditable ? ( + // 전체 FormField 컴포넌트와 아이템 선택 로직 개선 <FormField control={form.control} name={`items.${index}.itemCode`} @@ -401,7 +402,7 @@ export function RfqsItemsDialog({ const selected = filteredItems.find(it => it.code === field.value); return ( - <FormItem className="flex items-center gap-2 w-[250px]"> + <FormItem className="flex items-center gap-2 w-[250px]" style={{width:250}}> <FormControl> <Popover open={popoverOpen} onOpenChange={setPopoverOpen}> <PopoverTrigger asChild> @@ -413,12 +414,17 @@ export function RfqsItemsDialog({ variant="outline" role="combobox" aria-expanded={popoverOpen} - className="w-full justify-between" + className="flex items-center" data-error={!!form.formState.errors.items?.[index]?.itemCode} data-state={selected ? "filled" : "empty"} + style={{width:250}} > - {selected ? `${selected.code} - ${selected.name}` : "아이템 선택..."} - <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" /> + <div className="flex-1 overflow-hidden mr-2 text-left"> + <span className="block truncate" style={{width:200}}> + {selected ? `${selected.code} - ${selected.name}` : "아이템 선택..."} + </span> + </div> + <ChevronsUpDown className="h-4 w-4 flex-shrink-0 opacity-50" /> </Button> </PopoverTrigger> <PopoverContent className="w-[400px] p-0"> @@ -440,7 +446,9 @@ export function RfqsItemsDialog({ focusField(`input[name="items.${index}.description"]`); }} > - {label} + <div className="flex-1 overflow-hidden"> + <span className="block truncate">{label}</span> + </div> <Check className={ "ml-auto h-4 w-4" + @@ -486,9 +494,9 @@ export function RfqsItemsDialog({ render={({ field }) => ( <FormItem className="w-[400px]"> <FormControl> - <Input - className="w-full" - placeholder="아이템 상세 정보" + <Input + className="w-full" + placeholder="아이템 상세 정보" {...field} onKeyDown={(e) => { if (e.key === 'Enter') { @@ -650,7 +658,7 @@ export function RfqsItemsDialog({ </span> )} </div> - + {isEditable && ( <div className="text-xs text-muted-foreground"> <span className="inline-flex items-center gap-1 mr-2"> @@ -680,12 +688,12 @@ export function RfqsItemsDialog({ <TooltipContent>변경사항을 저장하지 않고 나가기</TooltipContent> </Tooltip> </TooltipProvider> - + <TooltipProvider> <Tooltip> <TooltipTrigger asChild> - <Button - type="submit" + <Button + type="submit" disabled={isSubmitting || (!form.formState.isDirty && deletedItemIds.length === 0) || !form.formState.isValid} > {isSubmitting ? ( diff --git a/lib/rfqs/table/add-rfq-dialog.tsx b/lib/rfqs/table/add-rfq-dialog.tsx index 1d824bc0..45390cd0 100644 --- a/lib/rfqs/table/add-rfq-dialog.tsx +++ b/lib/rfqs/table/add-rfq-dialog.tsx @@ -128,7 +128,11 @@ export function AddRfqDialog({ rfqType = RfqType.PURCHASE }: AddRfqDialogProps) }, [budgetaryRfqs, budgetarySearchTerm]); // 프로젝트 선택 처리 - const handleProjectSelect = (project: Project) => { + const handleProjectSelect = (project: Project | null) => { + if (project === null) { + return; + } + form.setValue("projectId", project.id); }; |
