summaryrefslogtreecommitdiff
path: root/lib/pq/table
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pq/table')
-rw-r--r--lib/pq/table/add-pq-list-dialog.tsx34
-rw-r--r--lib/pq/table/copy-pq-list-dialog.tsx80
-rw-r--r--lib/pq/table/pq-lists-columns.tsx127
-rw-r--r--lib/pq/table/pq-lists-table.tsx45
-rw-r--r--lib/pq/table/pq-lists-toolbar.tsx4
5 files changed, 212 insertions, 78 deletions
diff --git a/lib/pq/table/add-pq-list-dialog.tsx b/lib/pq/table/add-pq-list-dialog.tsx
index c1899a29..472a1b3d 100644
--- a/lib/pq/table/add-pq-list-dialog.tsx
+++ b/lib/pq/table/add-pq-list-dialog.tsx
@@ -10,13 +10,8 @@ import { DatePicker } from "@/components/ui/date-picker"
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog"
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"
import { Loader2, Plus } from "lucide-react"
+import { ProjectSelector } from "@/components/ProjectSelector"
-// 프로젝트 목록을 위한 임시 타입 (실제로는 projects에서 가져와야 함)
-interface Project {
- id: number
- name: string
- code: string
-}
const pqListFormSchema = z.object({
name: z.string().min(1, "PQ 목록 명을 입력해주세요"),
@@ -42,7 +37,6 @@ interface PqListFormProps {
open: boolean
onOpenChange: (open: boolean) => void
initialData?: Partial<PqListFormData> & { id?: number }
- projects?: Project[]
onSubmit: (data: PqListFormData & { id?: number }) => Promise<void>
isLoading?: boolean
}
@@ -57,7 +51,6 @@ export function AddPqDialog({
open,
onOpenChange,
initialData,
- projects = [],
onSubmit,
isLoading = false
}: PqListFormProps) {
@@ -162,23 +155,13 @@ export function AddPqDialog({
<FormLabel className="flex items-center gap-1">
프로젝트 <span className="text-red-500">*</span>
</FormLabel>
- <Select
- onValueChange={(value) => field.onChange(parseInt(value))}
- defaultValue={field.value?.toString()}
- >
- <FormControl>
- <SelectTrigger>
- <SelectValue placeholder="프로젝트를 선택하세요" />
- </SelectTrigger>
- </FormControl>
- <SelectContent>
- {projects.map((project) => (
- <SelectItem key={project.id} value={project.id.toString()}>
- {project.code} - {project.name}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
+ <FormControl>
+ <ProjectSelector
+ selectedProjectId={field.value}
+ onProjectSelect={(project) => field.onChange(project.id)}
+ placeholder="프로젝트를 선택하세요"
+ />
+ </FormControl>
<FormMessage />
</FormItem>
)}
@@ -200,6 +183,7 @@ export function AddPqDialog({
date={field.value ?? undefined}
onSelect={(date) => field.onChange(date ?? null)}
placeholder="유효일 선택"
+ minDate={new Date()}
/>
</FormControl>
<FormMessage />
diff --git a/lib/pq/table/copy-pq-list-dialog.tsx b/lib/pq/table/copy-pq-list-dialog.tsx
index 647ab1a3..51b7eed1 100644
--- a/lib/pq/table/copy-pq-list-dialog.tsx
+++ b/lib/pq/table/copy-pq-list-dialog.tsx
@@ -33,11 +33,17 @@ const copyPqSchema = z.object({
sourcePqListId: z.number({
required_error: "복사할 PQ 목록을 선택해주세요"
}),
- targetProjectId: z.number({
- required_error: "대상 프로젝트를 선택해주세요"
- }),
+ targetProjectId: z.number().optional(),
validTo: z.date(),
newName: z.string(),
+}).refine((data) => {
+ // 미실사 PQ가 아닌 경우에만 targetProjectId 필수
+ if (data.targetProjectId !== undefined) return true
+ // 미실사 PQ인 경우 targetProjectId는 선택사항
+ return true
+}, {
+ message: "프로젝트 PQ인 경우 대상 프로젝트를 선택해야 합니다",
+ path: ["targetProjectId"]
})
type CopyPqFormData = z.infer<typeof copyPqSchema>
@@ -106,36 +112,6 @@ export function CopyPqDialog({
<Form {...form}>
<form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-6">
- {/* 대상 프로젝트 선택 */}
- <FormField
- control={form.control}
- name="targetProjectId"
- render={({ field }) => (
- <FormItem>
- <FormLabel className="flex items-center gap-1">
- 대상 프로젝트 <span className="text-red-500">*</span>
- </FormLabel>
- <Select
- onValueChange={(value) => field.onChange(parseInt(value))}
- defaultValue={field.value?.toString()}
- >
- <FormControl>
- <SelectTrigger>
- <SelectValue placeholder="PQ를 적용할 프로젝트를 선택하세요" />
- </SelectTrigger>
- </FormControl>
- <SelectContent>
- {projects.map((project) => (
- <SelectItem key={project.id} value={project.id.toString()}>
- {project.code} - {project.name}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- <FormMessage />
- </FormItem>
- )}
- />
{/* 복사할 PQ 목록 선택 */}
<FormField
control={form.control}
@@ -155,7 +131,9 @@ export function CopyPqDialog({
</SelectTrigger>
</FormControl>
<SelectContent>
- {pqLists.map((pqList) => (
+ {pqLists
+ .filter(pqList => pqList.type !== "GENERAL") // 일반 PQ 제외
+ .map((pqList) => (
<SelectItem key={pqList.id} value={pqList.id.toString()}>
<div className="flex items-center gap-2">
<Badge className={typeColors[pqList.type]}>
@@ -184,6 +162,39 @@ export function CopyPqDialog({
</FormItem>
)}
/>
+ {/* 대상 프로젝트 선택 (미실사 PQ가 아닌 경우에만) */}
+ {selectedPqList?.type !== "NON_INSPECTION" && (
+ <FormField
+ control={form.control}
+ name="targetProjectId"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel className="flex items-center gap-1">
+ 대상 프로젝트 <span className="text-red-500">*</span>
+ </FormLabel>
+ <Select
+ onValueChange={(value) => field.onChange(parseInt(value))}
+ defaultValue={field.value?.toString()}
+ >
+ <FormControl>
+ <SelectTrigger>
+ <SelectValue placeholder="PQ를 적용할 프로젝트를 선택하세요" />
+ </SelectTrigger>
+ </FormControl>
+ <SelectContent>
+ {projects.map((project) => (
+ <SelectItem key={project.id} value={project.id.toString()}>
+ {project.code} - {project.name}
+ </SelectItem>
+ ))}
+ </SelectContent>
+ </Select>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ )}
+
{/* 새 PQ 목록 명 */}
<FormField
control={form.control}
@@ -214,6 +225,7 @@ export function CopyPqDialog({
date={field.value ?? undefined}
onSelect={(date) => field.onChange(date ?? null)}
placeholder="유효기간 선택"
+ minDate={new Date()}
/>
</FormControl>
<FormMessage />
diff --git a/lib/pq/table/pq-lists-columns.tsx b/lib/pq/table/pq-lists-columns.tsx
index 1c401fac..a9262a12 100644
--- a/lib/pq/table/pq-lists-columns.tsx
+++ b/lib/pq/table/pq-lists-columns.tsx
@@ -13,9 +13,19 @@ import {
DropdownMenuTrigger
} from "@/components/ui/dropdown-menu"
import { Button } from "@/components/ui/button"
-import React from "react"
+import React, { useMemo, useState } from "react"
import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header"
import { Checkbox } from "@/components/ui/checkbox"
+import {
+ Sheet,
+ SheetContent,
+ SheetDescription,
+ SheetFooter,
+ SheetHeader,
+ SheetTitle,
+} from "@/components/ui/sheet"
+import { DatePicker } from "@/components/ui/date-picker"
+import { toast } from "sonner"
export interface PQList {
id: number
@@ -48,6 +58,84 @@ const typeColors = {
interface GetColumnsProps {
setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<PQList> | null>>
}
+
+// 유효일 수정 시트 컴포넌트
+interface EditValidToSheetProps {
+ pqList: PQList | null
+ open: boolean
+ onOpenChange: (open: boolean) => void
+ onUpdate: (pqListId: number, newValidTo: Date | null) => Promise<void>
+}
+
+export function EditValidToSheet({ pqList, open, onOpenChange, onUpdate }: EditValidToSheetProps) {
+ const [newValidTo, setNewValidTo] = useState<Date | null>(pqList?.validTo || null)
+ const [isLoading, setIsLoading] = useState(false)
+
+ const handleSave = async () => {
+ if (!pqList) return
+
+ setIsLoading(true)
+ try {
+ await onUpdate(pqList.id, newValidTo)
+ onOpenChange(false)
+ } catch (error) {
+ console.error("유효일 수정 실패:", error)
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ return (
+ <Sheet open={open} onOpenChange={onOpenChange}>
+ <SheetContent>
+ <SheetHeader>
+ <SheetTitle>유효일 수정</SheetTitle>
+ <SheetDescription>
+ {pqList && (
+ <>
+ <strong>{pqList.name}</strong>의 유효일을 수정합니다.
+ </>
+ )}
+ </SheetDescription>
+ </SheetHeader>
+
+ <div className="py-6">
+ <div className="space-y-4">
+ <div>
+ <label className="text-sm font-medium">현재 유효일</label>
+ <div className="mt-1 p-2 bg-muted rounded-md">
+ {pqList?.validTo ? formatDate(pqList.validTo, "ko-KR") : "설정되지 않음"}
+ </div>
+ </div>
+
+ <div>
+ <label className="text-sm font-medium">새 유효일</label>
+ <div className="mt-1">
+ <DatePicker
+ date={newValidTo ?? undefined}
+ onSelect={(date) => setNewValidTo(date ?? null)}
+ placeholder="새 유효일을 선택하세요"
+ minDate={new Date()}
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <SheetFooter>
+ <Button variant="outline" onClick={() => onOpenChange(false)}>
+ 취소
+ </Button>
+ <Button onClick={handleSave} disabled={isLoading}>
+ {isLoading && <div className="mr-2 h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />}
+ 저장
+ </Button>
+ </SheetFooter>
+ </SheetContent>
+ </Sheet>
+ )
+}
+
export function createPQListsColumns({
setRowAction
}: GetColumnsProps): ColumnDef<PQList>[] {
@@ -122,17 +210,23 @@ export function createPQListsColumns({
),
cell: ({ row }) => {
const validTo = row.getValue("validTo") as Date | null
- const now = new Date()
- const isExpired = validTo && validTo < now
-
- const formattedDate = validTo ? formatDate(validTo, "ko-KR") : "-"
-
+
+ const dateInfo = useMemo(() => {
+ if (!validTo) return { formattedDate: "-", isExpired: false }
+
+ const now = new Date()
+ const isExpired = validTo < now
+ const formattedDate = formatDate(validTo, "ko-KR")
+
+ return { formattedDate, isExpired }
+ }, [validTo])
+
return (
<div className="text-sm">
- <span className={isExpired ? "text-red-600 font-medium" : ""}>
- {formattedDate}
+ <span className={dateInfo.isExpired ? "text-red-600 font-medium" : ""}>
+ {dateInfo.formattedDate}
</span>
- {isExpired && (
+ {dateInfo.isExpired && (
<Badge variant="destructive" className="ml-2 text-xs">
만료
</Badge>
@@ -168,14 +262,20 @@ export function createPQListsColumns({
header: ({ column }) => (
<DataTableColumnHeaderSimple column={column} title="생성일" />
),
- cell: ({ row }) => formatDate(row.getValue("createdAt"), "ko-KR"),
+ cell: ({ row }) => {
+ const createdAt = row.getValue("createdAt") as Date
+ return useMemo(() => formatDate(createdAt, "ko-KR"), [createdAt])
+ },
},
{
accessorKey: "updatedAt",
header: ({ column }) => (
<DataTableColumnHeaderSimple column={column} title="변경일" />
),
- cell: ({ row }) => formatDate(row.getValue("updatedAt"), "ko-KR"),
+ cell: ({ row }) => {
+ const updatedAt = row.getValue("updatedAt") as Date
+ return useMemo(() => formatDate(updatedAt, "ko-KR"), [updatedAt])
+ },
},
{
id: "actions",
@@ -196,6 +296,11 @@ export function createPQListsColumns({
>
상세보기
</DropdownMenuItem>
+ <DropdownMenuItem
+ onSelect={() => setRowAction({ row, type: "editValidTo" })}
+ >
+ 유효일 수정
+ </DropdownMenuItem>
{/* <DropdownMenuSeparator />
<DropdownMenuItem
onSelect={() => setRowAction({ row, type: "delete" })}
diff --git a/lib/pq/table/pq-lists-table.tsx b/lib/pq/table/pq-lists-table.tsx
index c5fd82a5..1be0a1c7 100644
--- a/lib/pq/table/pq-lists-table.tsx
+++ b/lib/pq/table/pq-lists-table.tsx
@@ -13,10 +13,12 @@ import {
deletePQListsAction,
copyPQListAction,
togglePQListsAction,
+ updatePqValidToAction,
} from "@/lib/pq/service"
import { CopyPqDialog } from "./copy-pq-list-dialog"
import { AddPqDialog } from "./add-pq-list-dialog"
import { PQListsToolbarActions } from "./pq-lists-toolbar"
+import { EditValidToSheet } from "./pq-lists-columns"
import type { DataTableRowAction } from "@/types/table"
interface Project {
@@ -34,10 +36,12 @@ export function PqListsTable({ promises }: PqListsTableProps) {
const [rowAction, setRowAction] = React.useState<DataTableRowAction<PQList> | null>(null)
const [createDialogOpen, setCreateDialogOpen] = React.useState(false)
const [copyDialogOpen, setCopyDialogOpen] = React.useState(false)
+ const [editValidToSheetOpen, setEditValidToSheetOpen] = React.useState(false)
+ const [selectedPqList, setSelectedPqList] = React.useState<PQList | null>(null)
const [isPending, startTransition] = React.useTransition()
const [{ data, pageCount }, projects] = React.use(promises)
- const activePqLists = data.filter((item) => !item.isDeleted)
+ // const activePqLists = data.filter((item) => !item.isDeleted)
const columns = React.useMemo(() => createPQListsColumns({ setRowAction }), [setRowAction])
@@ -116,15 +120,38 @@ export function PqListsTable({ promises }: PqListsTableProps) {
})
}
+ const handleUpdateValidTo = React.useCallback(async (pqListId: number, newValidTo: Date | null) => {
+ startTransition(async () => {
+ try {
+ const result = await updatePqValidToAction({ pqListId, validTo: newValidTo })
+ if (result.success) {
+ toast.success(result.message || "유효일이 성공적으로 수정되었습니다")
+ setEditValidToSheetOpen(false)
+ setSelectedPqList(null)
+ router.refresh()
+ } else {
+ toast.error(`유효일 수정 실패: ${result.error}`)
+ }
+ } catch (error) {
+ console.error("유효일 수정 실패:", error)
+ toast.error("유효일 수정 실패")
+ }
+ })
+ }, [])
+
React.useEffect(() => {
if (!rowAction) return
- const id = rowAction.row.original.id
+ const pqList = rowAction.row.original
switch (rowAction.type) {
case "view":
- router.push(`/evcp/pq-criteria/${id}`)
+ router.push(`/evcp/pq-criteria/${pqList.id}`)
break
case "delete":
- handleDelete([id])
+ handleDelete([pqList.id])
+ break
+ case "editValidTo":
+ setSelectedPqList(pqList)
+ setEditValidToSheetOpen(true)
break
}
setRowAction(null)
@@ -153,18 +180,24 @@ export function PqListsTable({ promises }: PqListsTableProps) {
open={createDialogOpen}
onOpenChange={setCreateDialogOpen}
onSubmit={handleCreate}
- projects={projects}
isLoading={isPending}
/>
<CopyPqDialog
open={copyDialogOpen}
onOpenChange={setCopyDialogOpen}
- pqLists={activePqLists}
+ pqLists={data}
projects={projects}
onCopy={handleCopy}
isLoading={isPending}
/>
+
+ <EditValidToSheet
+ pqList={selectedPqList}
+ open={editValidToSheetOpen}
+ onOpenChange={setEditValidToSheetOpen}
+ onUpdate={handleUpdateValidTo}
+ />
</>
)
}
diff --git a/lib/pq/table/pq-lists-toolbar.tsx b/lib/pq/table/pq-lists-toolbar.tsx
index 3a85327d..1feb9a1a 100644
--- a/lib/pq/table/pq-lists-toolbar.tsx
+++ b/lib/pq/table/pq-lists-toolbar.tsx
@@ -2,7 +2,7 @@
import * as React from "react"
import { Button } from "@/components/ui/button"
-import { Trash, CopyPlus, Plus } from "lucide-react"
+import { Trash, CopyPlus, Plus, RefreshCw } from "lucide-react"
import { type Table } from "@tanstack/react-table"
import type { PQList } from "./pq-lists-columns"
// import { PqListForm } from "./add-pq-list-dialog"
@@ -44,7 +44,7 @@ export function PQListsToolbarActions({
size="sm"
onClick={() => onToggleActive(selected, newState!)}
>
- <Trash className="mr-2 h-4 w-4" />
+ <RefreshCw className="mr-2 h-4 w-4" />
{toggleLabel}
</Button>
)}