summaryrefslogtreecommitdiff
path: root/lib/pq
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-10-27 10:03:06 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-10-27 10:03:06 +0000
commita3525f8bdfcf849cc1716fab81cb8facadbe9a8e (patch)
tree0b5b534e92bcfe188d4906db7d16c37044262c2f /lib/pq
parente87b7b06d92dc7e7235ecda24c212169f30e82ec (diff)
(최겸) 구매 협력업체 관리(PQ/실사관리, 정기평가 협력업체 제출 상세 dialog 개발,
Diffstat (limited to 'lib/pq')
-rw-r--r--lib/pq/pq-criteria/add-pq-dialog.tsx2
-rw-r--r--lib/pq/pq-criteria/pq-table-column.tsx50
-rw-r--r--lib/pq/pq-criteria/pq-table-toolbar-actions.tsx48
-rw-r--r--lib/pq/pq-criteria/pq-table.tsx10
-rw-r--r--lib/pq/pq-criteria/update-pq-sheet.tsx1
-rw-r--r--lib/pq/pq-review-table-new/edit-investigation-dialog.tsx2
-rw-r--r--lib/pq/pq-review-table-new/request-investigation-dialog.tsx6
-rw-r--r--lib/pq/pq-review-table-new/send-results-dialog.tsx8
-rw-r--r--lib/pq/pq-review-table-new/vendors-table-columns.tsx80
-rw-r--r--lib/pq/service.ts583
-rw-r--r--lib/pq/table/pq-lists-table.tsx2
11 files changed, 492 insertions, 300 deletions
diff --git a/lib/pq/pq-criteria/add-pq-dialog.tsx b/lib/pq/pq-criteria/add-pq-dialog.tsx
index 33e656c2..660eb360 100644
--- a/lib/pq/pq-criteria/add-pq-dialog.tsx
+++ b/lib/pq/pq-criteria/add-pq-dialog.tsx
@@ -68,7 +68,9 @@ const inputFormatOptions = [
{ value: "FILE", label: "파일" },
{ value: "EMAIL", label: "이메일" },
{ value: "PHONE", label: "전화번호" },
+ { value: "FAX", label: "팩스번호" },
{ value: "NUMBER", label: "숫자" },
+ { value: "NUMBER_WITH_UNIT", label: "숫자+단위" },
{ value: "TEXT_FILE", label: "텍스트 + 파일" },
];
diff --git a/lib/pq/pq-criteria/pq-table-column.tsx b/lib/pq/pq-criteria/pq-table-column.tsx
index 32d6cc32..ed1180f7 100644
--- a/lib/pq/pq-criteria/pq-table-column.tsx
+++ b/lib/pq/pq-criteria/pq-table-column.tsx
@@ -18,13 +18,16 @@ import { Button } from "@/components/ui/button"
import { Ellipsis } from "lucide-react"
import { Badge } from "@/components/ui/badge"
import { PqCriterias } from "@/db/schema/pq"
+import { toast } from "sonner"
interface GetColumnsProps {
setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<PqCriterias> | null>>
+ pqListInfo?: Awaited<ReturnType<typeof import("../service").getPQListInfo>>
}
export function getColumns({
setRowAction,
+ pqListInfo,
}: GetColumnsProps): ColumnDef<PqCriterias>[] {
return [
{
@@ -205,6 +208,12 @@ export function getColumns({
id: "actions",
enableHiding: false,
cell: function Cell({ row }) {
+ const isActive = pqListInfo?.success && pqListInfo.data.status === "ACTIVE";
+
+ const handleRestrictedAction = () => {
+ toast.error("활성화된 PQ 목록은 수정할 수 없습니다. 먼저 PQ 목록을 비활성화해주세요.");
+ };
+
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
@@ -217,18 +226,37 @@ export function getColumns({
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-40">
- <DropdownMenuItem
- onSelect={() => setRowAction({ row, type: "update" })}
- >
- Edit
- </DropdownMenuItem>
+ {isActive ? (
+ <DropdownMenuItem
+ onSelect={handleRestrictedAction}
+ className="text-muted-foreground"
+ >
+ Edit
+ </DropdownMenuItem>
+ ) : (
+ <DropdownMenuItem
+ onSelect={() => setRowAction({ row, type: "update" })}
+ >
+ Edit
+ </DropdownMenuItem>
+ )}
- <DropdownMenuItem
- onSelect={() => setRowAction({ row, type: "delete" })}
- >
- Delete
- <DropdownMenuShortcut>⌘⌫</DropdownMenuShortcut>
- </DropdownMenuItem>
+ {isActive ? (
+ <DropdownMenuItem
+ onSelect={handleRestrictedAction}
+ className="text-muted-foreground"
+ >
+ Delete
+ <DropdownMenuShortcut>⌘⌫</DropdownMenuShortcut>
+ </DropdownMenuItem>
+ ) : (
+ <DropdownMenuItem
+ onSelect={() => setRowAction({ row, type: "delete" })}
+ >
+ Delete
+ <DropdownMenuShortcut>⌘⌫</DropdownMenuShortcut>
+ </DropdownMenuItem>
+ )}
</DropdownMenuContent>
</DropdownMenu>
)
diff --git a/lib/pq/pq-criteria/pq-table-toolbar-actions.tsx b/lib/pq/pq-criteria/pq-table-toolbar-actions.tsx
index f168b83d..cdc4f813 100644
--- a/lib/pq/pq-criteria/pq-table-toolbar-actions.tsx
+++ b/lib/pq/pq-criteria/pq-table-toolbar-actions.tsx
@@ -17,37 +17,59 @@ import { type Table } from "@tanstack/react-table"
import { DeletePqsDialog } from "./delete-pqs-dialog"
import { AddPqDialog } from "./add-pq-dialog"
import { PqCriterias } from "@/db/schema/pq"
+import { toast } from "sonner"
// import { ImportPqButton } from "./import-pq-button"
// import { exportPqTemplate } from "./pq-excel-template"
interface PqTableToolbarActionsProps {
table: Table<PqCriterias>
pqListId: number
+ pqListInfo: Awaited<ReturnType<typeof import("../service").getPQListInfo>>
}
export function PqTableToolbarActions({
table,
- pqListId
+ pqListId,
+ pqListInfo
}: PqTableToolbarActionsProps) {
- // const [refreshKey, setRefreshKey] = React.useState(0)
+ // PQ 리스트가 ACTIVE 상태인지 확인
+ const isActive = pqListInfo.success && pqListInfo.data.status === "ACTIVE";
- // // Import 성공 후 테이블 갱신
- // const handleImportSuccess = () => {
- // setRefreshKey(prev => prev + 1)
- // }
+ // ACTIVE 상태일 때 기능 제한
+ const handleRestrictedAction = () => {
+ toast.error("활성화된 PQ 목록은 수정할 수 없습니다. 먼저 PQ 목록을 비활성화해주세요.");
+ };
return (
<div className="flex items-center gap-2">
{table.getFilteredSelectedRowModel().rows.length > 0 ? (
- <DeletePqsDialog
- pqs={table
- .getFilteredSelectedRowModel()
- .rows.map((row) => row.original)}
- onSuccess={() => table.toggleAllRowsSelected(false)}
- />
+ isActive ? (
+ <button
+ onClick={handleRestrictedAction}
+ className="inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-9 px-3 text-destructive hover:text-destructive"
+ >
+ PQ 항목 삭제
+ </button>
+ ) : (
+ <DeletePqsDialog
+ pqs={table
+ .getFilteredSelectedRowModel()
+ .rows.map((row) => row.original)}
+ onSuccess={() => table.toggleAllRowsSelected(false)}
+ />
+ )
) : null}
- <AddPqDialog pqListId={pqListId} />
+ {isActive ? (
+ <button
+ onClick={handleRestrictedAction}
+ className="inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground hover:bg-primary/90 h-9 px-3"
+ >
+ PQ 항목 추가
+ </button>
+ ) : (
+ <AddPqDialog pqListId={pqListId} />
+ )}
{/* Import 버튼 */}
{/* <ImportPqButton
diff --git a/lib/pq/pq-criteria/pq-table.tsx b/lib/pq/pq-criteria/pq-table.tsx
index e0e3dee5..187a727b 100644
--- a/lib/pq/pq-criteria/pq-table.tsx
+++ b/lib/pq/pq-criteria/pq-table.tsx
@@ -18,7 +18,7 @@ import { getColumns } from "./pq-table-column"
import { UpdatePqSheet } from "./update-pq-sheet"
interface DocumentListTableProps {
- promises: Promise<[Awaited<ReturnType<typeof getPQsByListId>>]>
+ promises: Promise<[Awaited<ReturnType<typeof getPQsByListId>>, Awaited<ReturnType<typeof getPQListInfo>>]>
pqListId: number
}
@@ -27,14 +27,14 @@ export function PqsTable({
pqListId
}: DocumentListTableProps) {
// 1) 데이터를 가져옴 (server component -> use(...) pattern)
- const [{ data, pageCount }] = React.use(promises)
+ const [{ data, pageCount }, pqListInfo] = React.use(promises)
const [rowAction, setRowAction] = React.useState<DataTableRowAction<PqCriterias> | null>(null)
const columns = React.useMemo(
- () => getColumns({ setRowAction }),
- [setRowAction]
+ () => getColumns({ setRowAction, pqListInfo }),
+ [setRowAction, pqListInfo]
)
// Filter fields
@@ -105,7 +105,7 @@ export function PqsTable({
filterFields={advancedFilterFields}
shallow={false}
>
- <PqTableToolbarActions table={table} pqListId={pqListId}/>
+ <PqTableToolbarActions table={table} pqListId={pqListId} pqListInfo={pqListInfo}/>
</DataTableAdvancedToolbar>
</DataTable>
diff --git a/lib/pq/pq-criteria/update-pq-sheet.tsx b/lib/pq/pq-criteria/update-pq-sheet.tsx
index 245627e6..fb298e9b 100644
--- a/lib/pq/pq-criteria/update-pq-sheet.tsx
+++ b/lib/pq/pq-criteria/update-pq-sheet.tsx
@@ -63,6 +63,7 @@ const inputFormatOptions = [
{ value: "EMAIL", label: "이메일" },
{ value: "PHONE", label: "전화번호" },
{ value: "NUMBER", label: "숫자" },
+ { value: "NUMBER_WITH_UNIT", label: "숫자+단위" },
{ value: "TEXT_FILE", label: "텍스트 + 파일" }
];
diff --git a/lib/pq/pq-review-table-new/edit-investigation-dialog.tsx b/lib/pq/pq-review-table-new/edit-investigation-dialog.tsx
index c5470e47..c4057798 100644
--- a/lib/pq/pq-review-table-new/edit-investigation-dialog.tsx
+++ b/lib/pq/pq-review-table-new/edit-investigation-dialog.tsx
@@ -224,7 +224,7 @@ export function EditInvestigationDialog({
name="confirmedAt"
render={({ field }) => (
<FormItem className="flex flex-col">
- <FormLabel>실사 확정일</FormLabel>
+ <FormLabel>실사 계획 확정일</FormLabel>
<Popover>
<PopoverTrigger asChild>
<FormControl>
diff --git a/lib/pq/pq-review-table-new/request-investigation-dialog.tsx b/lib/pq/pq-review-table-new/request-investigation-dialog.tsx
index 6941adbb..aaf10a71 100644
--- a/lib/pq/pq-review-table-new/request-investigation-dialog.tsx
+++ b/lib/pq/pq-review-table-new/request-investigation-dialog.tsx
@@ -55,7 +55,7 @@ const requestInvestigationFormSchema = z.object({
required_error: "QM 담당자를 선택해주세요.",
}),
forecastedAt: z.date({
- required_error: "실사 예정일을 선택해주세요.",
+ required_error: "실사 수행 예정일을 선택해주세요.",
}),
investigationAddress: z.string().min(1, "실사 장소를 입력해주세요."),
investigationMethod: z.string().optional(),
@@ -189,7 +189,7 @@ export function RequestInvestigationDialog({
name="forecastedAt"
render={({ field }) => (
<FormItem className="flex flex-col">
- <FormLabel>실사 예정일</FormLabel>
+ <FormLabel>실사 수행 예정일</FormLabel>
<Popover>
<PopoverTrigger asChild>
<FormControl>
@@ -201,7 +201,7 @@ export function RequestInvestigationDialog({
{field.value ? (
format(field.value, "yyyy년 MM월 dd일")
) : (
- <span>실사 예정일을 선택하세요</span>
+ <span>실사 수행 예정일을 선택하세요</span>
)}
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
</Button>
diff --git a/lib/pq/pq-review-table-new/send-results-dialog.tsx b/lib/pq/pq-review-table-new/send-results-dialog.tsx
index 3c8614cc..6c75e6ca 100644
--- a/lib/pq/pq-review-table-new/send-results-dialog.tsx
+++ b/lib/pq/pq-review-table-new/send-results-dialog.tsx
@@ -123,7 +123,9 @@ export function SendResultsDialog({
</div>
<div className="grid grid-cols-3 gap-4">
<div className="font-medium text-muted-foreground">Vendor</div>
- <div className="col-span-2">{result.vendorCode} | {result.vendorName}</div>
+ <div className="col-span-2">
+ {(result.vendorCode === "N/A" ? "미등록" : result.vendorCode)} | {result.vendorName}
+ </div>
</div>
<div className="grid grid-cols-3 gap-4">
<div className="font-medium text-muted-foreground">수신자</div>
@@ -151,7 +153,9 @@ export function SendResultsDialog({
</div>
{result.investigationNotes && (
<div className="grid grid-cols-3 gap-4">
- <div className="font-medium text-muted-foreground">실사합격조건</div>
+ <div className="font-medium text-muted-foreground">
+ {result.auditResult.includes("Pass") ? "QM 의견" : "실사합격조건"}
+ </div>
<div className="col-span-2">{result.investigationNotes}</div>
</div>
)}
diff --git a/lib/pq/pq-review-table-new/vendors-table-columns.tsx b/lib/pq/pq-review-table-new/vendors-table-columns.tsx
index ffa15e56..30b1c83f 100644
--- a/lib/pq/pq-review-table-new/vendors-table-columns.tsx
+++ b/lib/pq/pq-review-table-new/vendors-table-columns.tsx
@@ -3,6 +3,11 @@
import * as React from "react"
import { type DataTableRowAction } from "@/types/table"
import { type ColumnDef } from "@tanstack/react-table"
+
+// ColumnDef 타입 확장
+type ExtendedColumnDef<T> = ColumnDef<T> & {
+ excelHeader?: string;
+}
import { Ellipsis, Eye, FileEdit, Trash2, Building2, FileText, Edit } from "lucide-react"
import { formatDate } from "@/lib/utils"
@@ -116,11 +121,11 @@ function getStatusBadge(status: string) {
/**
* tanstack table 컬럼 정의
*/
-export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef<PQSubmission>[] {
+export function getColumns({ setRowAction, router }: GetColumnsProps): ExtendedColumnDef<PQSubmission>[] {
// ----------------------------------------------------------------
// 1) select 컬럼 (체크박스)
// ----------------------------------------------------------------
- const selectColumn: ColumnDef<PQSubmission> = {
+ const selectColumn: ExtendedColumnDef<PQSubmission> = {
id: "select",
header: ({ table }) => {
const selectedRows = table.getSelectedRowModel().rows;
@@ -180,7 +185,7 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef
// --------------------------
// --------------------------------------
- const pqNoColumn: ColumnDef<PQSubmission> = {
+ const pqNoColumn: ExtendedColumnDef<PQSubmission> = {
accessorKey: "pqNumber",
header: ({ column }) => (
<DataTableColumnHeaderSimple column={column} title="PQ No." />
@@ -190,10 +195,11 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef
<span className="font-medium">{row.getValue("pqNumber")}</span>
</div>
),
+ excelHeader: "PQ No.",
}
// 협력업체 컬럼
- const vendorColumn: ColumnDef<PQSubmission> = {
+ const vendorColumn: ExtendedColumnDef<PQSubmission> = {
accessorKey: "vendorName",
header: ({ column }) => (
<DataTableColumnHeaderSimple column={column} title="협력업체" />
@@ -206,10 +212,11 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef
),
enableSorting: true,
enableHiding: true,
+ excelHeader: "협력업체",
}
// PQ 유형 컬럼
- const typeColumn: ColumnDef<PQSubmission> = {
+ const typeColumn: ExtendedColumnDef<PQSubmission> = {
accessorKey: "type",
header: ({ column }) => (
<DataTableColumnHeaderSimple column={column} title="PQ 유형" />
@@ -233,10 +240,11 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef
},
enableSorting: true,
enableHiding: true,
+ excelHeader: "PQ 유형",
}
// 프로젝트 컬럼
- const projectColumn: ColumnDef<PQSubmission> = {
+ const projectColumn: ExtendedColumnDef<PQSubmission> = {
accessorKey: "projectName",
header: ({ column }) => (
<DataTableColumnHeaderSimple column={column} title="프로젝트" />
@@ -260,10 +268,11 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef
},
enableSorting: true,
enableHiding: true,
+ excelHeader: "프로젝트",
}
// 상태 컬럼
- const statusColumn: ColumnDef<PQSubmission> = {
+ const statusColumn: ExtendedColumnDef<PQSubmission> = {
accessorKey: "combinedStatus",
header: ({ column }) => (
<DataTableColumnHeaderSimple column={column} title="진행현황" />
@@ -278,6 +287,7 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef
},
enableSorting: true,
enableHiding: true,
+ excelHeader: "진행현황",
};
// PQ 상태와 실사 상태를 결합하는 헬퍼 함수
@@ -371,7 +381,7 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef
// };
- const evaluationResultColumn: ColumnDef<PQSubmission> = {
+ const evaluationResultColumn: ExtendedColumnDef<PQSubmission> = {
accessorKey: "evaluationResult",
header: ({ column }) => (
<DataTableColumnHeaderSimple column={column} title="평가 결과" />
@@ -401,10 +411,11 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef
},
enableSorting: true,
enableHiding: true,
+ excelHeader: "평가 결과",
};
// 답변 수 컬럼
- const answerCountColumn: ColumnDef<PQSubmission> = {
+ const answerCountColumn: ExtendedColumnDef<PQSubmission> = {
accessorKey: "answerCount",
header: ({ column }) => (
<DataTableColumnHeaderSimple column={column} title="답변 수" />
@@ -416,9 +427,10 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef
</div>
)
},
+ excelHeader: "답변 수",
}
- const investigationAddressColumn: ColumnDef<PQSubmission> = {
+ const investigationAddressColumn: ExtendedColumnDef<PQSubmission> = {
accessorKey: "investigationAddress",
header: ({ column }) => (
<DataTableColumnHeaderSimple column={column} title="실사 주소" />
@@ -436,8 +448,9 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef
</div>
)
},
+ excelHeader: "실사 주소",
}
- const investigationRequestedAtColumn: ColumnDef<PQSubmission> = {
+ const investigationRequestedAtColumn: ExtendedColumnDef<PQSubmission> = {
accessorKey: "investigationRequestedAt",
header: ({ column }) => (
<DataTableColumnHeaderSimple column={column} title="실사 의뢰일" />
@@ -456,9 +469,10 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef
</div>
)
},
+ excelHeader: "실사 의뢰일",
}
- const investigationNotesColumn: ColumnDef<PQSubmission> = {
+ const investigationNotesColumn: ExtendedColumnDef<PQSubmission> = {
accessorKey: "investigationNotes",
header: ({ column }) => (
<DataTableColumnHeaderSimple column={column} title="QM 의견" />
@@ -476,8 +490,9 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef
</div>
)
},
+ excelHeader: "QM 의견",
}
- const investigationMethodColumn: ColumnDef<PQSubmission> = {
+ const investigationMethodColumn: ExtendedColumnDef<PQSubmission> = {
accessorKey: "investigationMethod",
header: ({ column }) => (
<DataTableColumnHeaderSimple column={column} title="QM실사방법" />
@@ -501,10 +516,11 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef
return <span>{investigation.investigationMethod}</span>;
}
},
+ excelHeader: "실사품목",
}
// 실사품목 컬럼
- const pqItemsColumn: ColumnDef<PQSubmission> = {
+ const pqItemsColumn: ExtendedColumnDef<PQSubmission> = {
accessorKey: "pqItems",
header: ({ column }) => (
<DataTableColumnHeaderSimple column={column} title="실사품목" />
@@ -536,10 +552,10 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef
- const investigationForecastedAtColumn: ColumnDef<PQSubmission> = {
+ const investigationForecastedAtColumn: ExtendedColumnDef<PQSubmission> = {
accessorKey: "investigationForecastedAt",
header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="실사 예정일" />
+ <DataTableColumnHeaderSimple column={column} title="실사 수행 예정일" />
),
cell: ({ row }) => {
const investigation = row.original.investigation;
@@ -555,12 +571,13 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef
</div>
)
},
+ excelHeader: "실사 수행 예정일",
}
- const investigationConfirmedAtColumn: ColumnDef<PQSubmission> = {
+ const investigationConfirmedAtColumn: ExtendedColumnDef<PQSubmission> = {
accessorKey: "investigationConfirmedAt",
header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="실사 확정일" />
+ <DataTableColumnHeaderSimple column={column} title="실사 계획 확정일" />
),
cell: ({ row }) => {
const investigation = row.original.investigation;
@@ -576,9 +593,10 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef
</div>
)
},
+ excelHeader: "실사 계획 확정일",
}
- const investigationCompletedAtColumn: ColumnDef<PQSubmission> = {
+ const investigationCompletedAtColumn: ExtendedColumnDef<PQSubmission> = {
accessorKey: "investigationCompletedAt",
header: ({ column }) => (
<DataTableColumnHeaderSimple column={column} title="실제 실사일" />
@@ -597,10 +615,11 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef
</div>
)
},
+ excelHeader: "실제 실사일",
}
// 제출일 컬럼
- const createdAtColumn: ColumnDef<PQSubmission> = {
+ const createdAtColumn: ExtendedColumnDef<PQSubmission> = {
accessorKey: "createdAt",
header: ({ column }) => (
<DataTableColumnHeaderSimple column={column} title="PQ 전송일" />
@@ -609,10 +628,11 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef
const dateVal = row.original.createdAt as Date
return formatDate(dateVal, 'KR')
},
+ excelHeader: "PQ 전송일",
}
// 제출일 컬럼
- const submittedAtColumn: ColumnDef<PQSubmission> = {
+ const submittedAtColumn: ExtendedColumnDef<PQSubmission> = {
accessorKey: "submittedAt",
header: ({ column }) => (
<DataTableColumnHeaderSimple column={column} title="PQ 회신일" />
@@ -621,10 +641,11 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef
const dateVal = row.original.submittedAt as Date
return dateVal ? formatDate(dateVal, 'KR') : "-"
},
+ excelHeader: "PQ 회신일",
}
// 승인/거부일 컬럼
- const approvalDateColumn: ColumnDef<PQSubmission> = {
+ const approvalDateColumn: ExtendedColumnDef<PQSubmission> = {
accessorKey: "approvedAt",
header: ({ column }) => (
<DataTableColumnHeaderSimple column={column} title="PQ 승인/거부일" />
@@ -638,12 +659,13 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef
}
return "-"
},
+ excelHeader: "PQ 승인/거부일",
}
// ----------------------------------------------------------------
// 3) actions 컬럼 (Dropdown 메뉴)
// ----------------------------------------------------------------
- const actionsColumn: ColumnDef<PQSubmission> = {
+ const actionsColumn: ExtendedColumnDef<PQSubmission> = {
id: "actions",
enableHiding: false,
cell: function Cell({ row }) {
@@ -676,7 +698,7 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef
) : (
<>
<Eye className="mr-2 h-4 w-4" />
- 보기
+ PQ 현황
</>
)}
</DropdownMenuItem>
@@ -697,7 +719,7 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef
}}
>
<Building2 className="mr-2 h-4 w-4" />
- 방문실사
+ 실사 정보 전달 및 요청
</DropdownMenuItem>
<DropdownMenuItem
onSelect={(e) => {
@@ -710,7 +732,7 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef
}}
>
<FileText className="mr-2 h-4 w-4" />
- 협력업체 정보 조회
+ 실사 실시 확정 정보
</DropdownMenuItem>
</>
)}
@@ -758,7 +780,7 @@ export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef
}
// 요청자 컬럼 추가
-const requesterColumn: ColumnDef<PQSubmission> = {
+const requesterColumn: ExtendedColumnDef<PQSubmission> = {
accessorKey: "requesterName",
header: ({ column }) => (
<DataTableColumnHeaderSimple column={column} title="PQ/실사 요청자" />
@@ -779,8 +801,9 @@ const requesterColumn: ColumnDef<PQSubmission> = {
? <span>{pqRequesterName}</span>
: <span className="text-muted-foreground">-</span>;
},
+ excelHeader: "PQ/실사 요청자",
};
-const qmManagerColumn: ColumnDef<PQSubmission> = {
+const qmManagerColumn: ExtendedColumnDef<PQSubmission> = {
accessorKey: "qmManager",
header: ({ column }) => (
<DataTableColumnHeaderSimple column={column} title="QM 담당자" />
@@ -801,6 +824,7 @@ const qmManagerColumn: ColumnDef<PQSubmission> = {
</div>
);
},
+ excelHeader: "QM 담당자",
};
diff --git a/lib/pq/service.ts b/lib/pq/service.ts
index 7296b836..54459a6c 100644
--- a/lib/pq/service.ts
+++ b/lib/pq/service.ts
@@ -5,7 +5,7 @@ import { CopyPqListInput, CreatePqListInput, UpdatePqValidToInput, copyPqListSch
import { unstable_cache } from "@/lib/unstable-cache";
import { filterColumns } from "@/lib/filter-columns";
import { getErrorMessage } from "@/lib/handle-error";
-import { asc, desc, ilike, inArray, and, gte, lte, not, or, eq, ne, count,isNull,SQL, sql, lt, isNotNull} from "drizzle-orm";
+import { asc, desc, ilike, inArray, and, gte, lte, not, or, eq, ne, count,isNull,SQL, sql, lt, gt, isNotNull} from "drizzle-orm";
import { z } from "zod"
import { revalidateTag, unstable_noStore, revalidatePath} from "next/cache";
import { format } from "date-fns"
@@ -91,13 +91,25 @@ export async function getPQProjectsByVendorId(vendorId: number): Promise<Project
export async function getPQDataByVendorId(
vendorId: number,
- projectId?: number
+ projectId?: number,
+ pqType?: "GENERAL" | "PROJECT" | "NON_INSPECTION"
): Promise<PQGroupData[]> {
try {
// 파라미터 유효성 검증
if (isNaN(vendorId)) {
throw new Error("Invalid vendorId parameter");
}
+
+ // 타입 결정 로직
+ let finalPqType: "GENERAL" | "PROJECT" | "NON_INSPECTION";
+ if (pqType) {
+ finalPqType = pqType;
+ } else if (projectId) {
+ finalPqType = "PROJECT";
+ } else {
+ finalPqType = "GENERAL";
+ }
+
// 기본 쿼리 구성
const selectObj = {
criteriaId: pqCriterias.id,
@@ -127,65 +139,45 @@ export async function getPQDataByVendorId(
fileSize: vendorCriteriaAttachments.fileSize,
};
- // Create separate queries for each case instead of modifying the same query variable
- if (projectId) {
- // 프로젝트별 PQ 쿼리 - PQ 리스트 기반으로 변경
- const rows = await db
- .select(selectObj)
- .from(pqCriterias)
- .innerJoin(
- pqLists,
- and(
- eq(pqCriterias.pqListId, pqLists.id),
- eq(pqLists.projectId, projectId),
- eq(pqLists.type, "PROJECT"),
- eq(pqLists.isDeleted, false)
- )
- )
- .leftJoin(
- vendorPqCriteriaAnswers,
- and(
- eq(pqCriterias.id, vendorPqCriteriaAnswers.criteriaId),
- eq(vendorPqCriteriaAnswers.vendorId, vendorId),
- eq(vendorPqCriteriaAnswers.projectId, projectId)
- )
- )
- .leftJoin(
- vendorCriteriaAttachments,
- eq(vendorPqCriteriaAnswers.id, vendorCriteriaAttachments.vendorCriteriaAnswerId)
- )
- .orderBy(pqCriterias.groupName, pqCriterias.code);
-
- return processQueryResults(rows);
- } else {
- // 일반 PQ 쿼리 - PQ 리스트 기반으로 변경
- const rows = await db
- .select(selectObj)
- .from(pqCriterias)
- .innerJoin(
- pqLists,
- and(
- eq(pqCriterias.pqListId, pqLists.id),
- eq(pqLists.type, "GENERAL"),
- eq(pqLists.isDeleted, false)
- )
- )
- .leftJoin(
- vendorPqCriteriaAnswers,
- and(
- eq(pqCriterias.id, vendorPqCriteriaAnswers.criteriaId),
- eq(vendorPqCriteriaAnswers.vendorId, vendorId),
- isNull(vendorPqCriteriaAnswers.projectId)
- )
- )
- .leftJoin(
- vendorCriteriaAttachments,
- eq(vendorPqCriteriaAnswers.id, vendorCriteriaAttachments.vendorCriteriaAnswerId)
- )
- .orderBy(pqCriterias.groupName, pqCriterias.code);
-
- return processQueryResults(rows);
+ // 타입별 쿼리 조건 구성
+ const pqListConditions = [
+ eq(pqCriterias.pqListId, pqLists.id),
+ eq(pqLists.type, finalPqType),
+ eq(pqLists.isDeleted, false)
+ ];
+
+ const answerConditions = [
+ eq(pqCriterias.id, vendorPqCriteriaAnswers.criteriaId),
+ eq(vendorPqCriteriaAnswers.vendorId, vendorId)
+ ];
+
+ // 프로젝트별 조건 추가
+ if (finalPqType === "PROJECT" && projectId) {
+ pqListConditions.push(eq(pqLists.projectId, projectId));
+ answerConditions.push(eq(vendorPqCriteriaAnswers.projectId, projectId));
+ } else if (finalPqType === "GENERAL" || finalPqType === "NON_INSPECTION") {
+ pqListConditions.push(isNull(pqLists.projectId));
+ answerConditions.push(isNull(vendorPqCriteriaAnswers.projectId));
}
+
+ const rows = await db
+ .select(selectObj)
+ .from(pqCriterias)
+ .innerJoin(
+ pqLists,
+ and(...pqListConditions)
+ )
+ .leftJoin(
+ vendorPqCriteriaAnswers,
+ and(...answerConditions)
+ )
+ .leftJoin(
+ vendorCriteriaAttachments,
+ eq(vendorPqCriteriaAnswers.id, vendorCriteriaAttachments.vendorCriteriaAnswerId)
+ )
+ .orderBy(pqCriterias.groupName, pqCriterias.code);
+
+ return processQueryResults(rows);
} catch (error) {
console.error("Error fetching PQ data:", error);
return [];
@@ -790,199 +782,199 @@ export async function uploadSHIMultipleFilesAction(files: File[], userId?: strin
}
}
-export async function getVendorsInPQ(input: GetVendorsSchema) {
- return unstable_cache(
- async () => {
- try {
- const offset = (input.page - 1) * input.perPage;
-
- // 1) 고급 필터
- const advancedWhere = filterColumns({
- table: vendors,
- filters: input.filters,
- joinOperator: input.joinOperator,
- });
-
- // 2) 글로벌 검색
- let globalWhere: SQL<unknown> | undefined = undefined;
- if (input.search) {
- const s = `%${input.search}%`;
- globalWhere = or(
- ilike(vendors.vendorName, s),
- ilike(vendors.vendorCode, s),
- ilike(vendors.email, s),
- ilike(vendors.status, s)
- );
- }
-
- // 트랜잭션 내에서 데이터 조회
- const { data, total } = await db.transaction(async (tx) => {
- // 협력업체 ID 모음 (중복 제거용)
- const vendorIds = new Set<number>();
+// export async function getVendorsInPQ(input: GetVendorsSchema) {
+// return unstable_cache(
+// async () => {
+// try {
+// const offset = (input.page - 1) * input.perPage;
+
+// // 1) 고급 필터
+// const advancedWhere = filterColumns({
+// table: vendors,
+// filters: input.filters,
+// joinOperator: input.joinOperator,
+// });
+
+// // 2) 글로벌 검색
+// let globalWhere: SQL<unknown> | undefined = undefined;
+// if (input.search) {
+// const s = `%${input.search}%`;
+// globalWhere = or(
+// ilike(vendors.vendorName, s),
+// ilike(vendors.vendorCode, s),
+// ilike(vendors.email, s),
+// ilike(vendors.status, s)
+// );
+// }
+
+// // 트랜잭션 내에서 데이터 조회
+// const { data, total } = await db.transaction(async (tx) => {
+// // 협력업체 ID 모음 (중복 제거용)
+// const vendorIds = new Set<number>();
- // 1-A) 일반 PQ 답변이 있는 협력업체 찾기 (status와 상관없이)
- const generalPqVendors = await tx
- .select({
- vendorId: vendorPqCriteriaAnswers.vendorId
- })
- .from(vendorPqCriteriaAnswers)
- .innerJoin(
- vendors,
- eq(vendorPqCriteriaAnswers.vendorId, vendors.id)
- )
- .where(
- and(
- isNull(vendorPqCriteriaAnswers.projectId), // 일반 PQ만 (프로젝트 PQ 아님)
- advancedWhere,
- globalWhere
- )
- )
- .groupBy(vendorPqCriteriaAnswers.vendorId); // 각 벤더당 한 번만 카운트
+// // 1-A) 일반 PQ 답변이 있는 협력업체 찾기 (status와 상관없이)
+// const generalPqVendors = await tx
+// .select({
+// vendorId: vendorPqCriteriaAnswers.vendorId
+// })
+// .from(vendorPqCriteriaAnswers)
+// .innerJoin(
+// vendors,
+// eq(vendorPqCriteriaAnswers.vendorId, vendors.id)
+// )
+// .where(
+// and(
+// isNull(vendorPqCriteriaAnswers.projectId), // 일반 PQ만 (프로젝트 PQ 아님)
+// advancedWhere,
+// globalWhere
+// )
+// )
+// .groupBy(vendorPqCriteriaAnswers.vendorId); // 각 벤더당 한 번만 카운트
- generalPqVendors.forEach(v => vendorIds.add(v.vendorId));
+// generalPqVendors.forEach(v => vendorIds.add(v.vendorId));
- // 1-B) 프로젝트 PQ 답변이 있는 협력업체 ID 조회 (status와 상관없이)
- const projectPqVendors = await tx
- .select({
- vendorId: vendorPQSubmissions.vendorId
- })
- .from(vendorPQSubmissions)
- .innerJoin(
- vendors,
- eq(vendorPQSubmissions.vendorId, vendors.id)
- )
- .where(
- and(
- eq(vendorPQSubmissions.type, "PROJECT"),
- // 최소한 IN_PROGRESS부터는 작업이 시작된 상태이므로 포함
- not(eq(vendorPQSubmissions.status, "REQUESTED")), // REQUESTED 상태는 제외
- advancedWhere,
- globalWhere
- )
- );
+// // 1-B) 프로젝트 PQ 답변이 있는 협력업체 ID 조회 (status와 상관없이)
+// const projectPqVendors = await tx
+// .select({
+// vendorId: vendorPQSubmissions.vendorId
+// })
+// .from(vendorPQSubmissions)
+// .innerJoin(
+// vendors,
+// eq(vendorPQSubmissions.vendorId, vendors.id)
+// )
+// .where(
+// and(
+// eq(vendorPQSubmissions.type, "PROJECT"),
+// // 최소한 IN_PROGRESS부터는 작업이 시작된 상태이므로 포함
+// not(eq(vendorPQSubmissions.status, "REQUESTED")), // REQUESTED 상태는 제외
+// advancedWhere,
+// globalWhere
+// )
+// );
- projectPqVendors.forEach(v => vendorIds.add(v.vendorId));
+// projectPqVendors.forEach(v => vendorIds.add(v.vendorId));
- // 중복 제거된 협력업체 ID 배열
- const uniqueVendorIds = Array.from(vendorIds);
+// // 중복 제거된 협력업체 ID 배열
+// const uniqueVendorIds = Array.from(vendorIds);
- // 총 개수 (중복 제거 후)
- const total = uniqueVendorIds.length;
+// // 총 개수 (중복 제거 후)
+// const total = uniqueVendorIds.length;
- if (total === 0) {
- return { data: [], total: 0 };
- }
+// if (total === 0) {
+// return { data: [], total: 0 };
+// }
- // 페이징 처리 (정렬 후 limit/offset 적용)
- const paginatedIds = uniqueVendorIds.slice(offset, offset + input.perPage);
+// // 페이징 처리 (정렬 후 limit/offset 적용)
+// const paginatedIds = uniqueVendorIds.slice(offset, offset + input.perPage);
- // 2) 페이징된 협력업체 상세 정보 조회
- const vendorsData = await selectVendors(tx, {
- where: inArray(vendors.id, paginatedIds),
- orderBy: input.sort.length > 0
- ? input.sort.map((item) =>
- item.desc ? desc(vendors.vendorName) : asc(vendors.vendorName)
- )
- : [asc(vendors.createdAt)],
- });
+// // 2) 페이징된 협력업체 상세 정보 조회
+// const vendorsData = await selectVendors(tx, {
+// where: inArray(vendors.id, paginatedIds),
+// orderBy: input.sort.length > 0
+// ? input.sort.map((item) =>
+// item.desc ? desc(vendors.vendorName) : asc(vendors.vendorName)
+// )
+// : [asc(vendors.createdAt)],
+// });
- // 3) 각 벤더별 PQ 상태 정보 추가
- const vendorsWithPqInfo = await Promise.all(
- vendorsData.map(async (vendor) => {
- // 3-A) 첨부 파일 조회
- const attachments = await tx
- .select({
- id: vendorAttachments.id,
- fileName: vendorAttachments.fileName,
- filePath: vendorAttachments.filePath,
- })
- .from(vendorAttachments)
- .where(eq(vendorAttachments.vendorId, vendor.id));
+// // 3) 각 벤더별 PQ 상태 정보 추가
+// const vendorsWithPqInfo = await Promise.all(
+// vendorsData.map(async (vendor) => {
+// // 3-A) 첨부 파일 조회
+// const attachments = await tx
+// .select({
+// id: vendorAttachments.id,
+// fileName: vendorAttachments.fileName,
+// filePath: vendorAttachments.filePath,
+// })
+// .from(vendorAttachments)
+// .where(eq(vendorAttachments.vendorId, vendor.id));
- // 3-B) 일반 PQ 제출 여부 확인 (PQ 답변이 있는지)
- const generalPqAnswers = await tx
- .select({ count: count() })
- .from(vendorPqCriteriaAnswers)
- .where(
- and(
- eq(vendorPqCriteriaAnswers.vendorId, vendor.id),
- isNull(vendorPqCriteriaAnswers.projectId)
- )
- );
+// // 3-B) 일반 PQ 제출 여부 확인 (PQ 답변이 있는지)
+// const generalPqAnswers = await tx
+// .select({ count: count() })
+// .from(vendorPqCriteriaAnswers)
+// .where(
+// and(
+// eq(vendorPqCriteriaAnswers.vendorId, vendor.id),
+// isNull(vendorPqCriteriaAnswers.projectId)
+// )
+// );
- const hasGeneralPq = generalPqAnswers[0]?.count > 0;
+// const hasGeneralPq = generalPqAnswers[0]?.count > 0;
- // 3-C) 프로젝트 PQ 정보 조회 (모든 상태 포함)
- const projectPqs = await tx
- .select({
- projectId: vendorPQSubmissions.projectId,
- projectName: projects.name,
- status: vendorPQSubmissions.status,
- submittedAt: vendorPQSubmissions.submittedAt,
- approvedAt: vendorPQSubmissions.approvedAt,
- rejectedAt: vendorPQSubmissions.rejectedAt
- })
- .from(vendorPQSubmissions)
- .innerJoin(
- projects,
- eq(vendorPQSubmissions.projectId, projects.id)
- )
- .where(
- and(
- eq(vendorPQSubmissions.vendorId, vendor.id),
- eq(vendorPQSubmissions.type, "PROJECT"),
- not(eq(vendorPQSubmissions.status, "REQUESTED")) // REQUESTED 상태는 제외
- )
- );
+// // 3-C) 프로젝트 PQ 정보 조회 (모든 상태 포함)
+// const projectPqs = await tx
+// .select({
+// projectId: vendorPQSubmissions.projectId,
+// projectName: projects.name,
+// status: vendorPQSubmissions.status,
+// submittedAt: vendorPQSubmissions.submittedAt,
+// approvedAt: vendorPQSubmissions.approvedAt,
+// rejectedAt: vendorPQSubmissions.rejectedAt
+// })
+// .from(vendorPQSubmissions)
+// .innerJoin(
+// projects,
+// eq(vendorPQSubmissions.projectId, projects.id)
+// )
+// .where(
+// and(
+// eq(vendorPQSubmissions.vendorId, vendor.id),
+// eq(vendorPQSubmissions.type, "PROJECT"),
+// not(eq(vendorPQSubmissions.status, "REQUESTED")) // REQUESTED 상태는 제외
+// )
+// );
- const hasProjectPq = projectPqs.length > 0;
+// const hasProjectPq = projectPqs.length > 0;
- // 프로젝트 PQ 상태별 카운트
- const projectPqStatusCounts = {
- inProgress: projectPqs.filter(p => p.status === "IN_PROGRESS").length,
- submitted: projectPqs.filter(p => p.status === "SUBMITTED").length,
- approved: projectPqs.filter(p => p.status === "APPROVED").length,
- rejected: projectPqs.filter(p => p.status === "REJECTED").length,
- total: projectPqs.length
- };
+// // 프로젝트 PQ 상태별 카운트
+// const projectPqStatusCounts = {
+// inProgress: projectPqs.filter(p => p.status === "IN_PROGRESS").length,
+// submitted: projectPqs.filter(p => p.status === "SUBMITTED").length,
+// approved: projectPqs.filter(p => p.status === "APPROVED").length,
+// rejected: projectPqs.filter(p => p.status === "REJECTED").length,
+// total: projectPqs.length
+// };
- // 3-D) PQ 상태 정보 추가
- return {
- ...vendor,
- hasAttachments: attachments.length > 0,
- attachmentsList: attachments,
- pqInfo: {
- hasGeneralPq,
- hasProjectPq,
- projectPqs,
- projectPqStatusCounts,
- // 현재 PQ 상태 (UI에 표시 용도)
- pqStatus: getPqStatusDisplay(vendor.status, hasGeneralPq, hasProjectPq, projectPqStatusCounts)
- }
- };
- })
- );
+// // 3-D) PQ 상태 정보 추가
+// return {
+// ...vendor,
+// hasAttachments: attachments.length > 0,
+// attachmentsList: attachments,
+// pqInfo: {
+// hasGeneralPq,
+// hasProjectPq,
+// projectPqs,
+// projectPqStatusCounts,
+// // 현재 PQ 상태 (UI에 표시 용도)
+// pqStatus: getPqStatusDisplay(vendor.status, hasGeneralPq, hasProjectPq, projectPqStatusCounts)
+// }
+// };
+// })
+// );
- return { data: vendorsWithPqInfo, total };
- });
-
- // 페이지 수
- const pageCount = Math.ceil(total / input.perPage);
-
- return { data, pageCount };
- } catch (err) {
- console.error("Error in getVendorsInPQ:", err);
- // 에러 발생 시
- return { data: [], pageCount: 0 };
- }
- },
- [JSON.stringify(input)], // 캐싱 키
- {
- revalidate: 3600,
- tags: ["vendors-in-pq", "project-pqs"], // revalidateTag 호출 시 무효화
- }
- )();
-}
+// return { data: vendorsWithPqInfo, total };
+// });
+
+// // 페이지 수
+// const pageCount = Math.ceil(total / input.perPage);
+
+// return { data, pageCount };
+// } catch (err) {
+// console.error("Error in getVendorsInPQ:", err);
+// // 에러 발생 시
+// return { data: [], pageCount: 0 };
+// }
+// },
+// [JSON.stringify(input)], // 캐싱 키
+// {
+// revalidate: 3600,
+// tags: ["vendors-in-pq", "project-pqs"], // revalidateTag 호출 시 무효화
+// }
+// )();
+// }
// PQ 상태 표시 함수
function getPqStatusDisplay(
@@ -3105,6 +3097,66 @@ export async function togglePQListsAction(ids: number[], newIsDeleted: boolean)
const session = await getServerSession(authOptions);
const userId = session?.user?.id ? Number(session.user.id) : null;
const now = new Date();
+
+ // 활성화하려는 경우 중복 활성화 체크
+ if (!newIsDeleted) {
+ // 선택된 PQ 리스트들의 정보를 먼저 가져옴
+ const selectedPqLists = await db
+ .select({
+ id: pqLists.id,
+ name: pqLists.name,
+ type: pqLists.type,
+ projectId: pqLists.projectId,
+ })
+ .from(pqLists)
+ .where(inArray(pqLists.id, ids));
+
+ // 현재 활성화된 PQ 리스트 확인
+ const activePqLists = await db
+ .select({
+ id: pqLists.id,
+ name: pqLists.name,
+ type: pqLists.type,
+ projectId: pqLists.projectId,
+ })
+ .from(pqLists)
+ .where(and(
+ eq(pqLists.isDeleted, false),
+ not(inArray(pqLists.id, ids))
+ ));
+
+ // 각 선택된 PQ 리스트에 대해 중복 체크
+ for (const selectedPq of selectedPqLists) {
+ // 일반 PQ 또는 미실사 PQ인 경우
+ if (selectedPq.type === "GENERAL" || selectedPq.type === "NON_INSPECTION") {
+ const activeSameType = activePqLists.filter(pq => pq.type === selectedPq.type);
+
+ if (activeSameType.length > 0) {
+ const activeNames = activeSameType.map(pq => pq.name).join(", ");
+ return {
+ success: false,
+ error: `${selectedPq.type === "GENERAL" ? "일반" : "미실사"} PQ는 하나만 활성화할 수 있습니다.먼저 활성화된 ${selectedPq.type === "GENERAL" ? "일반" : "미실사"} PQ를 비활성화한 후 활성화해주세요.`
+ };
+ }
+ }
+
+ // 프로젝트 PQ인 경우
+ if (selectedPq.type === "PROJECT" && selectedPq.projectId) {
+ const activeSameProject = activePqLists.filter(pq =>
+ pq.type === "PROJECT" && pq.projectId === selectedPq.projectId
+ );
+
+ if (activeSameProject.length > 0) {
+ const activeNames = activeSameProject.map(pq => pq.name).join(", ");
+ return {
+ success: false,
+ error: `프로젝트 PQ는 프로젝트별로 하나만 활성화할 수 있습니다. 먼저 활성화된 프로젝트 PQ를 비활성화한 후 활성화해주세요.`
+ };
+ }
+ }
+ }
+ }
+
const updated = await db
.update(pqLists)
.set({ isDeleted: newIsDeleted, updatedAt: now, updatedBy: userId })
@@ -3726,6 +3778,65 @@ export async function deletePQSubmissionAction(pqSubmissionId: number) {
}
// PQ 목록별 항목 조회 (특정 pqListId에 속한 PQ 항목들)
+// PQ 리스트 정보 조회 (상태 포함)
+export async function getPQListInfo(pqListId: number) {
+ return unstable_cache(
+ async () => {
+ try {
+ const pqList = await db
+ .select({
+ id: pqLists.id,
+ name: pqLists.name,
+ type: pqLists.type,
+ projectId: pqLists.projectId,
+ validTo: pqLists.validTo,
+ isDeleted: pqLists.isDeleted,
+ createdAt: pqLists.createdAt,
+ updatedAt: pqLists.updatedAt,
+ })
+ .from(pqLists)
+ .where(and(
+ eq(pqLists.id, pqListId),
+ eq(pqLists.isDeleted, false)
+ ))
+ .limit(1)
+ .then(rows => rows[0]);
+
+ if (!pqList) {
+ return {
+ success: false,
+ error: "PQ 목록을 찾을 수 없습니다"
+ };
+ }
+
+ // 현재 시간과 비교하여 상태 결정
+ const now = new Date();
+ const isValid = !pqList.validTo || pqList.validTo > now;
+ const status = isValid ? "ACTIVE" : "INACTIVE";
+
+ return {
+ success: true,
+ data: {
+ ...pqList,
+ status
+ }
+ };
+ } catch (error) {
+ console.error("Error in getPQListInfo:", error);
+ return {
+ success: false,
+ error: "PQ 목록 정보를 가져오는 중 오류가 발생했습니다"
+ };
+ }
+ },
+ [`pq-list-info-${pqListId}`],
+ {
+ tags: ["pq-lists"],
+ revalidate: 3600, // 1시간
+ }
+ )();
+}
+
export async function getPQsByListId(pqListId: number, input: GetPQSchema) {
return unstable_cache(
async () => {
diff --git a/lib/pq/table/pq-lists-table.tsx b/lib/pq/table/pq-lists-table.tsx
index 1be0a1c7..e7c0ab0d 100644
--- a/lib/pq/table/pq-lists-table.tsx
+++ b/lib/pq/table/pq-lists-table.tsx
@@ -85,7 +85,7 @@ export function PqListsTable({ promises }: PqListsTableProps) {
toast.success(newIsDeleted ? "PQ 목록이 비활성화되었습니다" : "PQ 목록이 활성화되었습니다")
router.refresh()
} else {
- toast.error("PQ 목록 상태 변경 실패")
+ toast.error(result.error || "PQ 목록 상태 변경 실패")
}
})
}