diff options
Diffstat (limited to 'lib/material/vendor-material/add-confirmed-material.tsx')
| -rw-r--r-- | lib/material/vendor-material/add-confirmed-material.tsx | 183 |
1 files changed, 183 insertions, 0 deletions
diff --git a/lib/material/vendor-material/add-confirmed-material.tsx b/lib/material/vendor-material/add-confirmed-material.tsx new file mode 100644 index 00000000..bc232a1b --- /dev/null +++ b/lib/material/vendor-material/add-confirmed-material.tsx @@ -0,0 +1,183 @@ +"use client"; + +import * as React from "react"; +import { useState } from "react"; +import { useSession } from "next-auth/react"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { Label } from "@/components/ui/label"; +import { toast } from "sonner"; +import { Plus, Loader2 } from "lucide-react"; +import { MaterialSelector } from "@/components/common/material/material-selector"; +import { MaterialSearchItem } from "@/lib/material/material-group-service"; +import { addConfirmedMaterial, VendorPossibleMaterial } from "../vendor-possible-material-service"; + +interface AddConfirmedMaterialProps { + vendorId: number; + existingConfirmedMaterials: VendorPossibleMaterial[]; + onMaterialAdded?: () => void; +} + +export function AddConfirmedMaterial({ + vendorId, + existingConfirmedMaterials, + onMaterialAdded, +}: AddConfirmedMaterialProps) { + const { data: session } = useSession(); + const [open, setOpen] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [selectedMaterials, setSelectedMaterials] = useState<MaterialSearchItem[]>([]); + + // 이미 등록된 자재그룹코드들의 Set + const existingMaterialCodes = new Set( + existingConfirmedMaterials.map(material => material.itemCode).filter(Boolean) + ); + + // 자재 선택 시 중복 체크 + const handleMaterialsChange = (materials: MaterialSearchItem[]) => { + // 이미 등록된 자재가 있는지 확인 + const duplicatedMaterials = materials.filter(material => + existingMaterialCodes.has(material.materialGroupCode) + ); + + if (duplicatedMaterials.length > 0) { + const duplicatedCodes = duplicatedMaterials.map(m => m.materialGroupCode).join(', '); + toast.error(`이미 등록된 자재그룹코드입니다: ${duplicatedCodes}`); + + // 중복되지 않은 자재만 선택 + const validMaterials = materials.filter(material => + !existingMaterialCodes.has(material.materialGroupCode) + ); + setSelectedMaterials(validMaterials); + } else { + setSelectedMaterials(materials); + } + }; + + const handleSubmit = async () => { + if (!session?.user) { + toast.error("로그인이 필요합니다."); + return; + } + + if (selectedMaterials.length === 0) { + toast.error("추가할 자재를 선택해주세요."); + return; + } + + setIsLoading(true); + + try { + // 선택된 자재들을 각각 추가 + for (const material of selectedMaterials) { + const materialData = { + itemCode: material.materialGroupCode, + itemName: material.materialName, + }; + + await addConfirmedMaterial( + vendorId, + materialData, + Number(session.user.id), + session.user.name || "알 수 없음" + ); + } + + toast.success(`${selectedMaterials.length}개의 자재가 확정정보에 추가되었습니다.`); + + // 폼 리셋 + setSelectedMaterials([]); + setOpen(false); + + // 부모 컴포넌트에 추가 완료 알림 + onMaterialAdded?.(); + + } catch (error) { + console.error("자재 추가 실패:", error); + + // 에러 메시지를 더 구체적으로 표시 + if (error instanceof Error) { + if (error.message.includes("이미 확정정보에 등록되어 있습니다")) { + toast.error(error.message); + } else { + toast.error(`자재 추가 실패: ${error.message}`); + } + } else { + toast.error("자재 추가 중 알 수 없는 오류가 발생했습니다."); + } + } finally { + setIsLoading(false); + } + }; + + return ( + <Dialog open={open} onOpenChange={setOpen}> + <DialogTrigger asChild> + <Button size="sm" className="gap-2"> + <Plus className="h-4 w-4" /> + 추가 등록 + </Button> + </DialogTrigger> + <DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto"> + <DialogHeader> + <DialogTitle>확정정보 자재 추가</DialogTitle> + <DialogDescription> + 구매담당자 권한으로 확정 공급품목을 추가합니다. 상세 정보는 I/F를 통해 업데이트됩니다. + </DialogDescription> + </DialogHeader> + + <div className="space-y-4"> + {/* 자재 선택 */} + <div className="space-y-2"> + <Label>자재 선택 *</Label> + <MaterialSelector + selectedMaterials={selectedMaterials} + onMaterialsChange={handleMaterialsChange} + singleSelect={false} + placeholder="자재그룹코드 또는 자재명으로 검색..." + noValuePlaceHolder="자재를 선택해주세요" + maxSelections={10} + closeOnSelect={false} + excludeMaterialCodes={existingMaterialCodes} + /> + {existingMaterialCodes.size > 0 && ( + <p className="text-xs text-muted-foreground"> + 💡 이미 등록된 자재그룹코드는 자동으로 제외됩니다. + </p> + )} + <p className="text-sm text-muted-foreground"> + 최대 10개까지 선택 가능합니다. 등록자는 현재 로그인한 사용자로 자동 설정됩니다. + </p> + </div> + </div> + + <DialogFooter> + <Button + type="button" + variant="outline" + onClick={() => setOpen(false)} + disabled={isLoading} + > + 취소 + </Button> + <Button + type="button" + onClick={handleSubmit} + disabled={isLoading || selectedMaterials.length === 0} + > + {isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />} + {isLoading ? "추가 중..." : "추가"} + </Button> + </DialogFooter> + </DialogContent> + </Dialog> + ); +} |
