summaryrefslogtreecommitdiff
path: root/lib/vendors/table/request-pq-dialog.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/vendors/table/request-pq-dialog.tsx')
-rw-r--r--lib/vendors/table/request-pq-dialog.tsx143
1 files changed, 130 insertions, 13 deletions
diff --git a/lib/vendors/table/request-pq-dialog.tsx b/lib/vendors/table/request-pq-dialog.tsx
index 226a053f..a0c24dc6 100644
--- a/lib/vendors/table/request-pq-dialog.tsx
+++ b/lib/vendors/table/request-pq-dialog.tsx
@@ -2,7 +2,7 @@
import * as React from "react"
import { type Row } from "@tanstack/react-table"
-import { Loader, SendHorizonal } from "lucide-react"
+import { Loader, SendHorizonal, Search, X, Plus } from "lucide-react"
import { toast } from "sonner"
import { useMediaQuery } from "@/hooks/use-media-query"
import { Button } from "@/components/ui/button"
@@ -35,6 +35,8 @@ import {
} from "@/components/ui/select"
import { Checkbox } from "@/components/ui/checkbox"
import { Label } from "@/components/ui/label"
+import { Input } from "@/components/ui/input"
+import { Badge } from "@/components/ui/badge"
import { Vendor } from "@/db/schema/vendors"
import { requestBasicContractInfo, requestPQVendors } from "../service"
import { getProjectsWithPQList } from "@/lib/pq/service"
@@ -43,6 +45,7 @@ import { useSession } from "next-auth/react"
import { DatePicker } from "@/components/ui/date-picker"
import { getALLBasicContractTemplates } from "@/lib/basic-contract/service"
import type { BasicContractTemplate } from "@/db/schema"
+import { searchItemsForPQ } from "@/lib/items/service"
// import { PQContractViewer } from "../pq-contract-viewer" // 더 이상 사용하지 않음
interface RequestPQDialogProps extends React.ComponentPropsWithoutRef<typeof Dialog> {
@@ -62,6 +65,12 @@ const AGREEMENT_LIST = [
"GTC 합의",
]
+// PQ 대상 품목 타입 정의
+interface PQItem {
+ itemCode: string
+ itemName: string
+}
+
export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...props }: RequestPQDialogProps) {
const [isApprovePending, startApproveTransition] = React.useTransition()
const isDesktop = useMediaQuery("(min-width: 640px)")
@@ -73,12 +82,42 @@ export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...pro
const [selectedProjectId, setSelectedProjectId] = React.useState<number | null>(null)
const [agreements, setAgreements] = React.useState<Record<string, boolean>>({})
const [extraNote, setExtraNote] = React.useState<string>("")
- const [pqItems, setPqItems] = React.useState<string>("")
+ const [pqItems, setPqItems] = React.useState<PQItem[]>([])
+
+ // 아이템 검색 관련 상태
+ const [itemSearchQuery, setItemSearchQuery] = React.useState<string>("")
+ const [filteredItems, setFilteredItems] = React.useState<PQItem[]>([])
+ const [showItemDropdown, setShowItemDropdown] = React.useState<boolean>(false)
const [isLoadingProjects, setIsLoadingProjects] = React.useState(false)
const [basicContractTemplates, setBasicContractTemplates] = React.useState<BasicContractTemplate[]>([])
const [selectedTemplateIds, setSelectedTemplateIds] = React.useState<number[]>([])
const [isLoadingTemplates, setIsLoadingTemplates] = React.useState(false)
+ // 아이템 검색 필터링
+ React.useEffect(() => {
+ if (itemSearchQuery.trim() === "") {
+ setFilteredItems([])
+ setShowItemDropdown(false)
+ return
+ }
+
+ const searchItems = async () => {
+ try {
+ const results = await searchItemsForPQ(itemSearchQuery)
+ setFilteredItems(results)
+ setShowItemDropdown(true)
+ } catch (error) {
+ console.error("아이템 검색 오류:", error)
+ toast.error("아이템 검색 중 오류가 발생했습니다.")
+ setFilteredItems([])
+ setShowItemDropdown(false)
+ }
+ }
+
+ // 디바운싱: 300ms 후에 검색 실행
+ const timeoutId = setTimeout(searchItems, 300)
+ return () => clearTimeout(timeoutId)
+ }, [itemSearchQuery])
React.useEffect(() => {
if (type === "PROJECT") {
@@ -103,13 +142,37 @@ export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...pro
setSelectedProjectId(null)
setAgreements({})
setDueDate(null)
- setPqItems("")
+ setPqItems([])
setExtraNote("")
setSelectedTemplateIds([])
-
+ setItemSearchQuery("")
+ setFilteredItems([])
+ setShowItemDropdown(false)
}
}, [props.open])
+ // 아이템 선택 함수
+ const handleSelectItem = (item: PQItem) => {
+ // 이미 선택된 아이템인지 확인
+ const isAlreadySelected = pqItems.some(selectedItem =>
+ selectedItem.itemCode === item.itemCode
+ )
+
+ if (!isAlreadySelected) {
+ setPqItems(prev => [...prev, item])
+ }
+
+ // 검색 초기화
+ setItemSearchQuery("")
+ setFilteredItems([])
+ setShowItemDropdown(false)
+ }
+
+ // 아이템 제거 함수
+ const handleRemoveItem = (itemCode: string) => {
+ setPqItems(prev => prev.filter(item => item.itemCode !== itemCode))
+ }
+
const onApprove = () => {
if (!type) return toast.error("PQ 유형을 선택하세요.")
if (type === "PROJECT" && !selectedProjectId) return toast.error("프로젝트를 선택하세요.")
@@ -128,7 +191,7 @@ export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...pro
projectId: type === "PROJECT" ? selectedProjectId : null,
type: type || "GENERAL",
extraNote,
- pqItems,
+ pqItems: JSON.stringify(pqItems),
templateId: selectedTemplateIds.length > 0 ? selectedTemplateIds[0] : null,
})
@@ -355,14 +418,68 @@ export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...pro
{/* PQ 대상품목 */}
<div className="space-y-2">
- <Label htmlFor="pqItems">PQ 대상품목</Label>
- <textarea
- id="pqItems"
- value={pqItems}
- onChange={(e) => setPqItems(e.target.value)}
- placeholder="PQ 대상품목을 입력하세요 (선택사항)"
- className="w-full rounded-md border px-3 py-2 text-sm min-h-20 resize-none"
- />
+ <Label>PQ 대상품목</Label>
+
+ {/* 선택된 아이템들 표시 */}
+ {pqItems.length > 0 && (
+ <div className="flex flex-wrap gap-2 mb-2">
+ {pqItems.map((item) => (
+ <Badge key={item.itemCode} variant="secondary" className="flex items-center gap-1">
+ <span className="text-xs">
+ {item.itemCode} - {item.itemName}
+ </span>
+ <Button
+ type="button"
+ variant="ghost"
+ size="sm"
+ className="h-4 w-4 p-0 hover:bg-destructive hover:text-destructive-foreground"
+ onClick={() => handleRemoveItem(item.itemCode)}
+ >
+ <X className="h-3 w-3" />
+ </Button>
+ </Badge>
+ ))}
+ </div>
+ )}
+
+ {/* 검색 입력 */}
+ <div className="relative">
+ <div className="relative">
+ <Input
+ placeholder="아이템 코드 또는 이름으로 검색하세요"
+ value={itemSearchQuery}
+ onChange={(e) => setItemSearchQuery(e.target.value)}
+ className="pl-9"
+ />
+ </div>
+
+ {/* 검색 결과 드롭다운 */}
+ {showItemDropdown && (
+ <div className="absolute top-full left-0 right-0 z-50 mt-1 max-h-48 overflow-y-auto bg-background border rounded-md shadow-lg">
+ {filteredItems.length > 0 ? (
+ filteredItems.map((item) => (
+ <button
+ key={item.itemCode}
+ type="button"
+ className="w-full px-3 py-2 text-left text-sm hover:bg-muted focus:bg-muted focus:outline-none"
+ onClick={() => handleSelectItem(item)}
+ >
+ <div className="font-medium">{item.itemCode}</div>
+ <div className="text-muted-foreground text-xs">{item.itemName}</div>
+ </button>
+ ))
+ ) : (
+ <div className="px-3 py-2 text-sm text-muted-foreground">
+ 검색 결과가 없습니다.
+ </div>
+ )}
+ </div>
+ )}
+ </div>
+
+ <div className="text-xs text-muted-foreground">
+ 아이템 코드나 이름을 입력하여 검색하고 선택하세요. (선택사항)
+ </div>
</div>
{/* 추가 안내사항 */}