summaryrefslogtreecommitdiff
path: root/components/common/selectors/procurement-item/procurement-item-selector-dialog-single.tsx
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-11-12 10:42:36 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-11-12 10:42:36 +0000
commit8642ee064ddf96f1db2b948b4cc8bbbd6cfee820 (patch)
tree36bd57d147ba929f1d72918d1fb91ad2c4778624 /components/common/selectors/procurement-item/procurement-item-selector-dialog-single.tsx
parent57ea2f740abf1c7933671561cfe0e421fb5ef3fc (diff)
(최겸) 구매 일반계약, 입찰 수정
Diffstat (limited to 'components/common/selectors/procurement-item/procurement-item-selector-dialog-single.tsx')
-rw-r--r--components/common/selectors/procurement-item/procurement-item-selector-dialog-single.tsx168
1 files changed, 168 insertions, 0 deletions
diff --git a/components/common/selectors/procurement-item/procurement-item-selector-dialog-single.tsx b/components/common/selectors/procurement-item/procurement-item-selector-dialog-single.tsx
new file mode 100644
index 00000000..dab65780
--- /dev/null
+++ b/components/common/selectors/procurement-item/procurement-item-selector-dialog-single.tsx
@@ -0,0 +1,168 @@
+"use client";
+
+import React, { useState, useCallback } from "react";
+import { Button } from "@/components/ui/button";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog";
+import { ProcurementItemSelector } from "./procurement-item-selector";
+import { ProcurementSearchItem } from "./procurement-item-service";
+
+export interface ProcurementItemSelectorDialogSingleProps {
+ triggerLabel?: string;
+ triggerVariant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link";
+ triggerSize?: "default" | "sm" | "lg" | "icon";
+ selectedProcurementItem?: ProcurementSearchItem | null;
+ onProcurementItemSelect?: (item: ProcurementSearchItem | null) => void;
+ title?: string;
+ description?: string;
+ showConfirmButtons?: boolean;
+}
+
+/**
+ * 품목 단일 선택 Dialog 컴포넌트
+ *
+ * @description
+ * - ProcurementItemSelector를 Dialog로 래핑한 단일 선택 컴포넌트
+ * - 버튼 클릭 시 Dialog가 열리고, 품목을 선택하면 Dialog가 닫히며 결과를 반환
+ *
+ * @ProcurementSearchItem_Structure
+ * 상태에서 관리되는 품목 객체의 형태:
+ * ```typescript
+ * interface ProcurementSearchItem {
+ * itemCode: string; // 품목코드
+ * itemName: string; // 품목명
+ * material?: string; // 재질
+ * specification?: string; // 규격
+ * unit?: string; // 단위
+ * displayText: string; // 표시용 텍스트 (code + " - " + name)
+ * }
+ * ```
+ *
+ * @state
+ * - open: Dialog 열림/닫힘 상태
+ * - selectedProcurementItem: 현재 선택된 품목 (단일)
+ * - tempSelectedProcurementItem: Dialog 내에서 임시로 선택된 품목 (확인 버튼 클릭 전까지)
+ *
+ * @callback
+ * - onProcurementItemSelect: 품목 선택 완료 시 호출되는 콜백
+ * - 매개변수: ProcurementSearchItem | null
+ * - 선택된 품목 정보 또는 null (선택 해제 시)
+ *
+ * @usage
+ * ```tsx
+ * <ProcurementItemSelectorDialogSingle
+ * triggerLabel="품목 선택"
+ * selectedProcurementItem={selectedProcurementItem}
+ * onProcurementItemSelect={(item) => {
+ * console.log('선택된 품목:', item);
+ * setSelectedProcurementItem(item);
+ * }}
+ * title="품목 선택"
+ * description="품목을 검색하고 선택해주세요."
+ * />
+ * ```
+ */
+export function ProcurementItemSelectorDialogSingle({
+ triggerLabel = "품목 선택",
+ triggerVariant = "outline",
+ triggerSize = "default",
+ selectedProcurementItem = null,
+ onProcurementItemSelect,
+ title = "품목 선택",
+ description = "품목을 검색하고 선택해주세요.",
+ showConfirmButtons = false,
+}: ProcurementItemSelectorDialogSingleProps) {
+ const [open, setOpen] = useState(false);
+ const [tempSelectedProcurementItem, setTempSelectedProcurementItem] =
+ useState<ProcurementSearchItem | null>(selectedProcurementItem);
+
+ // Dialog가 열릴 때 임시 선택 상태 초기화
+ const handleOpenChange = useCallback((newOpen: boolean) => {
+ setOpen(newOpen);
+ if (newOpen) {
+ // Dialog 열 때 현재 선택된 값으로 임시 상태 초기화
+ setTempSelectedProcurementItem(selectedProcurementItem);
+ }
+ }, [selectedProcurementItem]);
+
+ // 품목 선택 시 임시 상태 업데이트
+ const handleProcurementItemSelect = useCallback((item: ProcurementSearchItem | null) => {
+ setTempSelectedProcurementItem(item);
+
+ // 확인 버튼이 없는 경우 즉시 적용하고 Dialog 닫기
+ if (!showConfirmButtons) {
+ onProcurementItemSelect?.(item);
+ setOpen(false);
+ }
+ }, [onProcurementItemSelect, showConfirmButtons]);
+
+ // 확인 버튼 클릭 시 선택 적용
+ const handleConfirm = useCallback(() => {
+ onProcurementItemSelect?.(tempSelectedProcurementItem);
+ setOpen(false);
+ }, [onProcurementItemSelect, tempSelectedProcurementItem]);
+
+ // 취소 버튼 클릭 시 Dialog 닫기 (변경사항 적용 안 함)
+ const handleCancel = useCallback(() => {
+ setOpen(false);
+ }, []);
+
+ // 선택 해제
+ const handleClear = useCallback(() => {
+ const newSelection = null;
+ setTempSelectedProcurementItem(newSelection);
+
+ if (!showConfirmButtons) {
+ onProcurementItemSelect?.(newSelection);
+ setOpen(false);
+ }
+ }, [onProcurementItemSelect, showConfirmButtons]);
+
+ return (
+ <Dialog open={open} onOpenChange={handleOpenChange}>
+ <DialogTrigger asChild>
+ <Button variant={triggerVariant} size={triggerSize}>
+ {selectedProcurementItem ? (
+ <span className="truncate">
+ {`${selectedProcurementItem.itemCode} - ${selectedProcurementItem.itemName}`}
+ </span>
+ ) : (
+ <span className="text-muted-foreground">{triggerLabel}</span>
+ )}
+ </Button>
+ </DialogTrigger>
+ <DialogContent className="max-w-4xl max-h-[80vh] overflow-hidden flex flex-col">
+ <DialogHeader>
+ <DialogTitle>{title}</DialogTitle>
+ <DialogDescription>{description}</DialogDescription>
+ </DialogHeader>
+
+ <div className="flex-1 overflow-hidden">
+ <ProcurementItemSelector
+ selectedProcurementItem={tempSelectedProcurementItem}
+ onProcurementItemSelect={handleProcurementItemSelect}
+ onClear={handleClear}
+ />
+ </div>
+
+ {showConfirmButtons && (
+ <DialogFooter>
+ <Button variant="outline" onClick={handleCancel}>
+ 취소
+ </Button>
+ <Button onClick={handleConfirm}>
+ 확인
+ </Button>
+ </DialogFooter>
+ )}
+ </DialogContent>
+ </Dialog>
+ );
+}