summaryrefslogtreecommitdiff
path: root/components/common
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-12-08 14:19:37 +0900
committerjoonhoekim <26rote@gmail.com>2025-12-08 14:19:37 +0900
commit2ac7deb8494cf4123f0cff3321860585a44f157c (patch)
tree789b6980c8f863a0f675fad38c4a17d91ba28bf3 /components/common
parent71c0ba1f01b98770ec2c60cdb935ffb36c1830a9 (diff)
parente37cce51ccfa3dcb91904b2492df3a29970fadf7 (diff)
Merge remote-tracking branch 'origin/sec-patch' into table-v2
Diffstat (limited to 'components/common')
-rw-r--r--components/common/date-picker/date-picker-with-input.tsx322
-rw-r--r--components/common/date-picker/index.ts3
-rw-r--r--components/common/legal/cpvw-wab-qust-list-view-dialog.tsx364
-rw-r--r--components/common/selectors/procurement-item/procurement-item-selector-dialog-single.tsx4
4 files changed, 692 insertions, 1 deletions
diff --git a/components/common/date-picker/date-picker-with-input.tsx b/components/common/date-picker/date-picker-with-input.tsx
new file mode 100644
index 00000000..6e768601
--- /dev/null
+++ b/components/common/date-picker/date-picker-with-input.tsx
@@ -0,0 +1,322 @@
+"use client"
+
+import * as React from "react"
+import { format, parse, isValid } from "date-fns"
+import { ko } from "date-fns/locale"
+import { CalendarIcon, ChevronLeft, ChevronRight } from "lucide-react"
+import { DayPicker } from "react-day-picker"
+
+import { cn } from "@/lib/utils"
+import { Button, buttonVariants } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover"
+
+export interface DatePickerWithInputProps {
+ value?: Date
+ onChange?: (date: Date | undefined) => void
+ disabled?: boolean
+ placeholder?: string
+ className?: string
+ minDate?: Date
+ maxDate?: Date
+ dateFormat?: string
+ inputClassName?: string
+ locale?: "ko" | "en"
+}
+
+/**
+ * DatePickerWithInput - 캘린더 선택 및 직접 입력이 가능한 날짜 선택기
+ *
+ * 사용법:
+ * ```tsx
+ * <DatePickerWithInput
+ * value={selectedDate}
+ * onChange={(date) => setSelectedDate(date)}
+ * placeholder="날짜를 선택하세요"
+ * minDate={new Date()}
+ * />
+ * ```
+ */
+export function DatePickerWithInput({
+ value,
+ onChange,
+ disabled = false,
+ placeholder = "YYYY-MM-DD",
+ className,
+ minDate,
+ maxDate,
+ dateFormat = "yyyy-MM-dd",
+ inputClassName,
+ locale: localeProp = "en",
+}: DatePickerWithInputProps) {
+ const [open, setOpen] = React.useState(false)
+ const [inputValue, setInputValue] = React.useState<string>("")
+ const [month, setMonth] = React.useState<Date>(value || new Date())
+ const [hasError, setHasError] = React.useState(false)
+ const [errorMessage, setErrorMessage] = React.useState<string>("")
+
+ // 외부 value가 변경되면 inputValue도 업데이트
+ React.useEffect(() => {
+ if (value && isValid(value)) {
+ setInputValue(format(value, dateFormat))
+ setMonth(value)
+ setHasError(false)
+ setErrorMessage("")
+ } else {
+ setInputValue("")
+ }
+ }, [value, dateFormat])
+
+ // 날짜 유효성 검사 및 에러 메시지 설정
+ const validateDate = (date: Date): { valid: boolean; message: string } => {
+ if (minDate) {
+ const minDateStart = new Date(minDate)
+ minDateStart.setHours(0, 0, 0, 0)
+ const dateToCheck = new Date(date)
+ dateToCheck.setHours(0, 0, 0, 0)
+ if (dateToCheck < minDateStart) {
+ return {
+ valid: false,
+ message: `${format(minDate, dateFormat)} 이후 날짜를 선택해주세요`
+ }
+ }
+ }
+ if (maxDate) {
+ const maxDateEnd = new Date(maxDate)
+ maxDateEnd.setHours(23, 59, 59, 999)
+ if (date > maxDateEnd) {
+ return {
+ valid: false,
+ message: `${format(maxDate, dateFormat)} 이전 날짜를 선택해주세요`
+ }
+ }
+ }
+ return { valid: true, message: "" }
+ }
+
+ // 캘린더에서 날짜 선택
+ const handleCalendarSelect = React.useCallback((date: Date | undefined, e?: React.MouseEvent) => {
+ // 이벤트 전파 중지
+ if (e) {
+ e.preventDefault()
+ e.stopPropagation()
+ }
+
+ if (date) {
+ const validation = validateDate(date)
+ if (validation.valid) {
+ setInputValue(format(date, dateFormat))
+ setHasError(false)
+ setErrorMessage("")
+ onChange?.(date)
+ setMonth(date)
+ } else {
+ setHasError(true)
+ setErrorMessage(validation.message)
+ }
+ }
+ setOpen(false)
+ }, [dateFormat, onChange, minDate, maxDate])
+
+ // 직접 입력값 변경
+ const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ const newValue = e.target.value
+ setInputValue(newValue)
+
+ // 입력 중에는 에러 상태 초기화
+ if (hasError) {
+ setHasError(false)
+ setErrorMessage("")
+ }
+
+ // YYYY-MM-DD 형식인 경우에만 파싱 시도
+ if (/^\d{4}-\d{2}-\d{2}$/.test(newValue)) {
+ const parsedDate = parse(newValue, dateFormat, new Date())
+
+ if (isValid(parsedDate)) {
+ const validation = validateDate(parsedDate)
+ if (validation.valid) {
+ setHasError(false)
+ setErrorMessage("")
+ onChange?.(parsedDate)
+ setMonth(parsedDate)
+ } else {
+ setHasError(true)
+ setErrorMessage(validation.message)
+ }
+ } else {
+ setHasError(true)
+ setErrorMessage("유효하지 않은 날짜 형식입니다")
+ }
+ }
+ }
+
+ // 입력 완료 시 (blur) 유효성 검사
+ const handleInputBlur = () => {
+ if (!inputValue) {
+ setHasError(false)
+ setErrorMessage("")
+ onChange?.(undefined)
+ return
+ }
+
+ const parsedDate = parse(inputValue, dateFormat, new Date())
+
+ if (isValid(parsedDate)) {
+ const validation = validateDate(parsedDate)
+ if (validation.valid) {
+ setHasError(false)
+ setErrorMessage("")
+ onChange?.(parsedDate)
+ } else {
+ setHasError(true)
+ setErrorMessage(validation.message)
+ // 유효 범위를 벗어난 경우 입력값은 유지하되 에러 표시
+ }
+ } else {
+ // 유효하지 않은 형식인 경우
+ setHasError(true)
+ setErrorMessage("YYYY-MM-DD 형식으로 입력해주세요")
+ }
+ }
+
+ // 키보드 이벤트 처리 (Enter 키로 완료)
+ const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
+ if (e.key === "Enter") {
+ handleInputBlur()
+ }
+ }
+
+ // 날짜 비활성화 체크 (캘린더용)
+ const isDateDisabled = (date: Date) => {
+ if (disabled) return true
+ if (minDate) {
+ const minDateStart = new Date(minDate)
+ minDateStart.setHours(0, 0, 0, 0)
+ const dateToCheck = new Date(date)
+ dateToCheck.setHours(0, 0, 0, 0)
+ if (dateToCheck < minDateStart) return true
+ }
+ if (maxDate) {
+ const maxDateEnd = new Date(maxDate)
+ maxDateEnd.setHours(23, 59, 59, 999)
+ if (date > maxDateEnd) return true
+ }
+ return false
+ }
+
+ // 캘린더 버튼 클릭 핸들러
+ const handleCalendarButtonClick = (e: React.MouseEvent) => {
+ e.preventDefault()
+ e.stopPropagation()
+ setOpen(!open)
+ }
+
+ // Popover 상태 변경 핸들러
+ const handleOpenChange = (newOpen: boolean) => {
+ setOpen(newOpen)
+ }
+
+ return (
+ <div className={cn("relative", className)}>
+ <div className="flex items-center gap-1">
+ <Input
+ type="text"
+ value={inputValue}
+ onChange={handleInputChange}
+ onBlur={handleInputBlur}
+ onKeyDown={handleKeyDown}
+ placeholder={placeholder}
+ disabled={disabled}
+ className={cn(
+ "pr-10",
+ hasError && "border-red-500 focus-visible:ring-red-500",
+ inputClassName
+ )}
+ />
+ <Popover open={open} onOpenChange={handleOpenChange} modal={true}>
+ <PopoverTrigger asChild>
+ <Button
+ variant="ghost"
+ size="icon"
+ className="absolute right-0 h-full px-3 hover:bg-transparent"
+ disabled={disabled}
+ type="button"
+ onClick={handleCalendarButtonClick}
+ >
+ <CalendarIcon className={cn(
+ "h-4 w-4",
+ hasError ? "text-red-500" : "text-muted-foreground"
+ )} />
+ </Button>
+ </PopoverTrigger>
+ <PopoverContent
+ className="w-auto p-0"
+ align="end"
+ onPointerDownOutside={(e) => e.preventDefault()}
+ onInteractOutside={(e) => e.preventDefault()}
+ >
+ <div
+ onClick={(e) => e.stopPropagation()}
+ onMouseDown={(e) => e.stopPropagation()}
+ >
+ <DayPicker
+ mode="single"
+ selected={value}
+ onSelect={(date, selectedDay, activeModifiers, e) => {
+ handleCalendarSelect(date, e as unknown as React.MouseEvent)
+ }}
+ month={month}
+ onMonthChange={setMonth}
+ disabled={isDateDisabled}
+ locale={localeProp === "ko" ? ko : undefined}
+ showOutsideDays
+ className="p-3"
+ classNames={{
+ months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
+ month: "space-y-4",
+ caption: "flex justify-center pt-1 relative items-center",
+ caption_label: "text-sm font-medium",
+ nav: "space-x-1 flex items-center",
+ nav_button: cn(
+ buttonVariants({ variant: "outline" }),
+ "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
+ ),
+ nav_button_previous: "absolute left-1",
+ nav_button_next: "absolute right-1",
+ table: "w-full border-collapse space-y-1",
+ head_row: "flex",
+ head_cell: "text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]",
+ row: "flex w-full mt-2",
+ cell: "relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected])]:rounded-md",
+ day: cn(
+ buttonVariants({ variant: "ghost" }),
+ "h-8 w-8 p-0 font-normal aria-selected:opacity-100"
+ ),
+ day_selected: "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
+ day_today: "bg-accent text-accent-foreground",
+ day_outside: "text-muted-foreground opacity-50",
+ day_disabled: "text-muted-foreground opacity-50",
+ day_hidden: "invisible",
+ }}
+ components={{
+ IconLeft: () => <ChevronLeft className="h-4 w-4" />,
+ IconRight: () => <ChevronRight className="h-4 w-4" />,
+ }}
+ />
+ </div>
+ </PopoverContent>
+ </Popover>
+ </div>
+ {/* 에러 메시지 표시 */}
+ {hasError && errorMessage && (
+ <p className="text-xs text-red-500 mt-1">{errorMessage}</p>
+ )}
+ </div>
+ )
+}
+
diff --git a/components/common/date-picker/index.ts b/components/common/date-picker/index.ts
new file mode 100644
index 00000000..85c0c259
--- /dev/null
+++ b/components/common/date-picker/index.ts
@@ -0,0 +1,3 @@
+// 공용 날짜 선택기 컴포넌트
+export { DatePickerWithInput, type DatePickerWithInputProps } from './date-picker-with-input'
+
diff --git a/components/common/legal/cpvw-wab-qust-list-view-dialog.tsx b/components/common/legal/cpvw-wab-qust-list-view-dialog.tsx
new file mode 100644
index 00000000..aeefbb84
--- /dev/null
+++ b/components/common/legal/cpvw-wab-qust-list-view-dialog.tsx
@@ -0,0 +1,364 @@
+"use client"
+
+import * as React from "react"
+import { Loader, Database, Check } from "lucide-react"
+import { toast } from "sonner"
+import {
+ useReactTable,
+ getCoreRowModel,
+ getPaginationRowModel,
+ getFilteredRowModel,
+ ColumnDef,
+ flexRender,
+} from "@tanstack/react-table"
+
+import { Button } from "@/components/ui/button"
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog"
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table"
+import { Checkbox } from "@/components/ui/checkbox"
+import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"
+
+import { getCPVWWabQustListViewData, CPVWWabQustListView } from "@/lib/basic-contract/cpvw-service"
+
+interface CPVWWabQustListViewDialogProps {
+ onConfirm?: (selectedRows: CPVWWabQustListView[]) => void
+ requireSingleSelection?: boolean
+ triggerDisabled?: boolean
+ triggerTitle?: string
+}
+
+export function CPVWWabQustListViewDialog({
+ onConfirm,
+ requireSingleSelection = false,
+ triggerDisabled = false,
+ triggerTitle,
+}: CPVWWabQustListViewDialogProps) {
+ const [open, setOpen] = React.useState(false)
+ const [isLoading, setIsLoading] = React.useState(false)
+ const [data, setData] = React.useState<CPVWWabQustListView[]>([])
+ const [error, setError] = React.useState<string | null>(null)
+ const [rowSelection, setRowSelection] = React.useState<Record<string, boolean>>({})
+
+ const loadData = async () => {
+ setIsLoading(true)
+ setError(null)
+ try {
+ const result = await getCPVWWabQustListViewData()
+ if (result.success) {
+ setData(result.data)
+ if (result.isUsingFallback) {
+ toast.info("테스트 데이터를 표시합니다.")
+ }
+ } else {
+ setError(result.error || "데이터 로딩 실패")
+ toast.error(result.error || "데이터 로딩 실패")
+ }
+ } catch (err) {
+ const errorMessage = err instanceof Error ? err.message : "알 수 없는 오류"
+ setError(errorMessage)
+ toast.error(errorMessage)
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ React.useEffect(() => {
+ if (open) {
+ loadData()
+ } else {
+ // 다이얼로그 닫힐 때 데이터 초기화
+ setData([])
+ setError(null)
+ setRowSelection({})
+ }
+ }, [open])
+
+ // 테이블 컬럼 정의 (동적 생성)
+ const columns = React.useMemo<ColumnDef<CPVWWabQustListView>[]>(() => {
+ if (data.length === 0) return []
+
+ const dataKeys = Object.keys(data[0])
+
+ return [
+ {
+ id: "select",
+ header: ({ table }) => (
+ <Checkbox
+ checked={
+ table.getIsAllPageRowsSelected() ||
+ (table.getIsSomePageRowsSelected() && "indeterminate")
+ }
+ onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
+ aria-label="모든 행 선택"
+ />
+ ),
+ cell: ({ row }) => (
+ <Checkbox
+ checked={row.getIsSelected()}
+ onCheckedChange={(value) => row.toggleSelected(!!value)}
+ aria-label="행 선택"
+ />
+ ),
+ enableSorting: false,
+ enableHiding: false,
+ },
+ ...dataKeys.map((key) => ({
+ accessorKey: key,
+ header: key,
+ cell: ({ getValue }: any) => {
+ const value = getValue()
+ return value !== null && value !== undefined ? String(value) : ""
+ },
+ })),
+ ]
+ }, [data])
+
+ // 테이블 인스턴스 생성
+ const table = useReactTable({
+ data,
+ columns,
+ getCoreRowModel: getCoreRowModel(),
+ getPaginationRowModel: getPaginationRowModel(),
+ getFilteredRowModel: getFilteredRowModel(),
+ onRowSelectionChange: setRowSelection,
+ state: {
+ rowSelection,
+ },
+ })
+
+ // 선택된 행들 가져오기
+ const selectedRows = table.getFilteredSelectedRowModel().rows.map(row => row.original)
+
+ // 확인 버튼 핸들러
+ const handleConfirm = () => {
+ if (selectedRows.length === 0) {
+ toast.error("행을 선택해주세요.")
+ return
+ }
+
+ if (requireSingleSelection && selectedRows.length !== 1) {
+ toast.error("하나의 행만 선택해주세요.")
+ return
+ }
+
+ if (onConfirm) {
+ onConfirm(selectedRows)
+ toast.success(
+ requireSingleSelection
+ ? "선택한 행으로 준법문의 상태를 동기화합니다."
+ : `${selectedRows.length}개의 행을 선택했습니다.`
+ )
+ } else {
+ // 임시로 선택된 데이터 콘솔 출력
+ console.log("선택된 행들:", selectedRows)
+ toast.success(`${selectedRows.length}개의 행이 선택되었습니다. (콘솔 확인)`)
+ }
+
+ setOpen(false)
+ }
+
+ return (
+ <Dialog open={open} onOpenChange={setOpen}>
+ <DialogTrigger asChild>
+ <Button
+ variant="outline"
+ size="sm"
+ disabled={triggerDisabled}
+ title={triggerTitle}
+ >
+ <Database className="mr-2 size-4" aria-hidden="true" />
+ 준법문의 요청 데이터 조회
+ </Button>
+ </DialogTrigger>
+ <DialogContent className="max-w-7xl h-[90vh] flex flex-col overflow-hidden">
+ <DialogHeader>
+ <DialogTitle>준법문의 요청 데이터</DialogTitle>
+ <DialogDescription>
+ 준법문의 요청 데이터를 조회합니다.
+ {data.length > 0 && ` (${data.length}건, ${selectedRows.length}개 선택됨)`}
+ </DialogDescription>
+ </DialogHeader>
+
+ <div className="flex flex-col flex-1 min-h-0">
+ {isLoading ? (
+ <div className="flex items-center justify-center flex-1 min-h-[200px]">
+ <Loader className="mr-2 size-6 animate-spin" />
+ <span>데이터 로딩 중...</span>
+ </div>
+ ) : error ? (
+ <div className="flex items-center justify-center flex-1 min-h-[200px] text-red-500">
+ <span>오류: {error}</span>
+ </div>
+ ) : data.length === 0 ? (
+ <div className="flex items-center justify-center flex-1 min-h-[200px] text-muted-foreground">
+ <span>데이터가 없습니다.</span>
+ </div>
+ ) : (
+ <div className="flex flex-col flex-1 min-h-0">
+ {/* 테이블 영역 - 스크롤 가능 */}
+ <ScrollArea className="flex-1 overflow-auto border rounded-md">
+ <Table className="min-w-full">
+ <TableHeader className="sticky top-0 bg-background z-10">
+ {table.getHeaderGroups().map((headerGroup) => (
+ <TableRow key={headerGroup.id}>
+ {headerGroup.headers.map((header) => (
+ <TableHead key={header.id} className="font-medium bg-background">
+ {header.isPlaceholder
+ ? null
+ : flexRender(
+ header.column.columnDef.header,
+ header.getContext()
+ )}
+ </TableHead>
+ ))}
+ </TableRow>
+ ))}
+ </TableHeader>
+ <TableBody>
+ {table.getRowModel().rows?.length ? (
+ table.getRowModel().rows.map((row) => (
+ <TableRow
+ key={row.id}
+ data-state={row.getIsSelected() && "selected"}
+ >
+ {row.getVisibleCells().map((cell) => (
+ <TableCell key={cell.id} className="text-sm">
+ {flexRender(
+ cell.column.columnDef.cell,
+ cell.getContext()
+ )}
+ </TableCell>
+ ))}
+ </TableRow>
+ ))
+ ) : (
+ <TableRow>
+ <TableCell
+ colSpan={columns.length}
+ className="h-24 text-center"
+ >
+ 데이터가 없습니다.
+ </TableCell>
+ </TableRow>
+ )}
+ </TableBody>
+ </Table>
+ <ScrollBar orientation="horizontal" />
+ </ScrollArea>
+
+ {/* 페이지네이션 컨트롤 - 고정 영역 */}
+ <div className="flex items-center justify-between px-2 py-4 border-t bg-background flex-shrink-0">
+ <div className="flex-1 text-sm text-muted-foreground">
+ {table.getFilteredSelectedRowModel().rows.length}개 행 선택됨
+ </div>
+ <div className="flex items-center space-x-6 lg:space-x-8">
+ <div className="flex items-center space-x-2">
+ <p className="text-sm font-medium">페이지당 행 수</p>
+ <select
+ value={table.getState().pagination.pageSize}
+ onChange={(e) => {
+ table.setPageSize(Number(e.target.value))
+ }}
+ className="h-8 w-[70px] rounded border border-input bg-transparent px-3 py-1 text-sm ring-offset-background focus:ring-2 focus:ring-ring focus:ring-offset-2"
+ >
+ {[10, 20, 30, 40, 50].map((pageSize) => (
+ <option key={pageSize} value={pageSize}>
+ {pageSize}
+ </option>
+ ))}
+ </select>
+ </div>
+ <div className="flex w-[100px] items-center justify-center text-sm font-medium">
+ {table.getState().pagination.pageIndex + 1} /{" "}
+ {table.getPageCount()}
+ </div>
+ <div className="flex items-center space-x-2">
+ <Button
+ variant="outline"
+ className="h-8 w-8 p-0"
+ onClick={() => table.setPageIndex(0)}
+ disabled={!table.getCanPreviousPage()}
+ >
+ <span className="sr-only">첫 페이지로</span>
+ {"<<"}
+ </Button>
+ <Button
+ variant="outline"
+ className="h-8 w-8 p-0"
+ onClick={() => table.previousPage()}
+ disabled={!table.getCanPreviousPage()}
+ >
+ <span className="sr-only">이전 페이지</span>
+ {"<"}
+ </Button>
+ <Button
+ variant="outline"
+ className="h-8 w-8 p-0"
+ onClick={() => table.nextPage()}
+ disabled={!table.getCanNextPage()}
+ >
+ <span className="sr-only">다음 페이지</span>
+ {">"}
+ </Button>
+ <Button
+ variant="outline"
+ className="h-8 w-8 p-0"
+ onClick={() => table.setPageIndex(table.getPageCount() - 1)}
+ disabled={!table.getCanNextPage()}
+ >
+ <span className="sr-only">마지막 페이지로</span>
+ {">>"}
+ </Button>
+ </div>
+ </div>
+ </div>
+ </div>
+ )}
+ </div>
+
+ <DialogFooter className="gap-2 flex-shrink-0">
+ <Button variant="outline" onClick={() => setOpen(false)}>
+ 닫기
+ </Button>
+ <Button onClick={loadData} disabled={isLoading} variant="outline">
+ {isLoading ? (
+ <>
+ <Loader className="mr-2 size-4 animate-spin" />
+ 로딩 중...
+ </>
+ ) : (
+ "새로고침"
+ )}
+ </Button>
+ <Button
+ onClick={handleConfirm}
+ disabled={
+ requireSingleSelection
+ ? selectedRows.length !== 1
+ : selectedRows.length === 0
+ }
+ >
+ <Check className="mr-2 size-4" />
+ 확인 ({selectedRows.length})
+ </Button>
+ </DialogFooter>
+ </DialogContent>
+ </Dialog>
+ )
+}
+
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
index 84fd85ff..a1b98468 100644
--- a/components/common/selectors/procurement-item/procurement-item-selector-dialog-single.tsx
+++ b/components/common/selectors/procurement-item/procurement-item-selector-dialog-single.tsx
@@ -23,6 +23,7 @@ export interface ProcurementItemSelectorDialogSingleProps {
title?: string;
description?: string;
showConfirmButtons?: boolean;
+ disabled?: boolean;
}
/**
@@ -78,6 +79,7 @@ export function ProcurementItemSelectorDialogSingle({
title = "1회성 품목 선택",
description = "1회성 품목을 검색하고 선택해주세요.",
showConfirmButtons = false,
+ disabled = false,
}: ProcurementItemSelectorDialogSingleProps) {
const [open, setOpen] = useState(false);
const [tempSelectedProcurementItem, setTempSelectedProcurementItem] =
@@ -128,7 +130,7 @@ export function ProcurementItemSelectorDialogSingle({
return (
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogTrigger asChild>
- <Button variant={triggerVariant} size={triggerSize}>
+ <Button variant={triggerVariant} size={triggerSize} disabled={disabled}>
{selectedProcurementItem ? (
<span className="truncate">
{`${selectedProcurementItem.itemCode}`}