From 4e63d8427d26d0d1b366ddc53650e15f3481fc75 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Tue, 24 Jun 2025 01:44:03 +0000 Subject: (대표님/최겸) 20250624 작업사항 10시43분 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/evaluation-criteria/repository.ts | 198 +++++++ lib/evaluation-criteria/service.ts | 221 ++++++++ .../table/reg-eval-criteria-columns.tsx | 364 +++++++++++++ .../table/reg-eval-criteria-delete-dialog.tsx | 147 ++++++ .../table/reg-eval-criteria-form-sheet.tsx | 586 +++++++++++++++++++++ .../reg-eval-criteria-table-toolbar-actions.tsx | 161 ++++++ .../table/reg-eval-criteria-table.tsx | 189 +++++++ lib/evaluation-criteria/validations.ts | 41 ++ lib/evaluation-target-list/service.ts | 372 ++++++++++--- .../table/evaluation-target-table.tsx | 543 +++++++++---------- .../table/evaluation-targets-columns.tsx | 351 +++++------- .../table/evaluation-targets-filter-sheet.tsx | 2 +- .../table/evaluation-targets-toolbar-actions.tsx | 4 +- .../table/update-evaluation-target.tsx | 4 +- lib/evaluation/service.ts | 125 +++++ lib/evaluation/table/evaluation-columns.tsx | 35 +- lib/evaluation/table/evaluation-filter-sheet.tsx | 2 +- lib/evaluation/table/evaluation-table.tsx | 72 +-- lib/spread-js/fns.ts | 353 +++++++++++++ lib/tech-vendors/service.ts | 23 +- .../table/tech-vendors-table-columns.tsx | 5 - lib/tech-vendors/validations.ts | 3 +- 22 files changed, 3143 insertions(+), 658 deletions(-) create mode 100644 lib/evaluation-criteria/repository.ts create mode 100644 lib/evaluation-criteria/service.ts create mode 100644 lib/evaluation-criteria/table/reg-eval-criteria-columns.tsx create mode 100644 lib/evaluation-criteria/table/reg-eval-criteria-delete-dialog.tsx create mode 100644 lib/evaluation-criteria/table/reg-eval-criteria-form-sheet.tsx create mode 100644 lib/evaluation-criteria/table/reg-eval-criteria-table-toolbar-actions.tsx create mode 100644 lib/evaluation-criteria/table/reg-eval-criteria-table.tsx create mode 100644 lib/evaluation-criteria/validations.ts create mode 100644 lib/spread-js/fns.ts (limited to 'lib') diff --git a/lib/evaluation-criteria/repository.ts b/lib/evaluation-criteria/repository.ts new file mode 100644 index 00000000..d406f45a --- /dev/null +++ b/lib/evaluation-criteria/repository.ts @@ -0,0 +1,198 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +/* IMPORT */ +import { + asc, + count, + desc, + eq, +} from 'drizzle-orm'; +import { PgTransaction } from 'drizzle-orm/pg-core'; +import { + regEvalCriteria, + regEvalCriteriaDetails, + regEvalCriteriaView, + type NewRegEvalCriteria, + type NewRegEvalCriteriaDetails, +} from '@/db/schema'; + +// ---------------------------------------------------------------------------------------------------- + +/* SELECT VIEW TRANSACTION */ +async function selectRegEvalCriteria( + tx: PgTransaction, + params: { + where?: any; + orderBy?: (ReturnType | ReturnType)[]; + offset?: number; + limit?: number; + }, +) { + const { + where, + orderBy, + offset = 0, + limit = 10, + } = params; + const result = await tx + .select() + .from(regEvalCriteriaView) + .where(where) + .orderBy(...(orderBy ?? [])) + .offset(offset) + .limit(limit); + + return result; +} + +/* SELECT COUNT TRANSACTION */ +async function countRegEvalCriteria( + tx: PgTransaction, + where?: any, +) { + const result = await tx + .select({ count: count() }) + .from(regEvalCriteriaView) + .where(where); + + return result[0]?.count ?? 0; +} + +/* SELECT JOIN TRANSACTION */ +async function selectRegEvalCriteriaWithDetails( + tx: PgTransaction, + id: number, +) { + const criteria = await tx + .select() + .from(regEvalCriteria) + .where(eq(regEvalCriteria.id, id)); + + if (!criteria[0]) { + return null; + } + + const details = await tx + .select() + .from(regEvalCriteriaDetails) + .where(eq(regEvalCriteriaDetails.criteriaId, id)) + .orderBy(regEvalCriteriaDetails.orderIndex); + + return { + ...criteria[0], + criteriaDetails: details, + }; +} + +// ---------------------------------------------------------------------------------------------------- + +/* INSERT CRITERIA TRANSACTION */ +async function insertRegEvalCriteria( + tx: PgTransaction, + data: NewRegEvalCriteria, +) { + const [insertRes] = await tx + .insert(regEvalCriteria) + .values(data) + .returning(); + + return insertRes; +} + +/* INSERT CRITERIA DETAILS TRANSACTION */ +async function insertRegEvalCriteriaDetails( + tx: PgTransaction, + data: NewRegEvalCriteriaDetails, +) { + const [insertRes] = await tx + .insert(regEvalCriteriaDetails) + .values(data) + .returning(); + + return insertRes; +} + +// ---------------------------------------------------------------------------------------------------- + +/* UPDATE CRITERIA TRANSACTION */ +async function updateRegEvalCriteria( + tx: PgTransaction, + criteriaId: number, + data: Partial, +) { + const [updateRes] = await tx + .update(regEvalCriteria) + .set(data) + .where(eq(regEvalCriteria.id, criteriaId)) + .returning(); + + return updateRes; +} + +/* UPDATE CRITERIA DETAILS TRANSACTION */ +async function updateRegEvalCriteriaDetails( + tx: PgTransaction, + detailId: number, + data: Partial, +) { + const [updateRes] = await tx + .update(regEvalCriteriaDetails) + .set(data) + .where(eq(regEvalCriteriaDetails.id, detailId)) + .returning(); + + return updateRes; +} + +// ---------------------------------------------------------------------------------------------------- + +/* DELETE CRITERIA TRANSACTION */ +async function deleteRegEvalCriteria( + tx: PgTransaction, + criteriaId: number, +) { + const [deleteRes] = await tx + .delete(regEvalCriteria) + .where(eq(regEvalCriteria.id, criteriaId)) + .returning(); + + return deleteRes; +} + +/* DELETE CRITERIA TRANSACTION */ +async function deleteRegEvalCriteriaDetails( + tx: PgTransaction, + datailId: number, +) { + const [deleteRes] = await tx + .delete(regEvalCriteriaDetails) + .where(eq(regEvalCriteriaDetails.id, datailId)) + .returning(); + + return deleteRes; +} + +/* DELETE ALL TRANSACTION */ +async function deleteAllRegEvalCriteria( + tx: PgTransaction, +) { + const [deleteRes] = await tx.delete(regEvalCriteria); + + return deleteRes; +} + +// ---------------------------------------------------------------------------------------------------- + +/* EXPORT */ +export { + countRegEvalCriteria, + deleteAllRegEvalCriteria, + deleteRegEvalCriteria, + deleteRegEvalCriteriaDetails, + insertRegEvalCriteria, + insertRegEvalCriteriaDetails, + selectRegEvalCriteria, + selectRegEvalCriteriaWithDetails, + updateRegEvalCriteria, + updateRegEvalCriteriaDetails, +}; \ No newline at end of file diff --git a/lib/evaluation-criteria/service.ts b/lib/evaluation-criteria/service.ts new file mode 100644 index 00000000..ec23c9e4 --- /dev/null +++ b/lib/evaluation-criteria/service.ts @@ -0,0 +1,221 @@ +'use server'; + +/* IMPORT */ +import { + and, + asc, + desc, + ilike, + or, +} from 'drizzle-orm'; +import { + countRegEvalCriteria, + deleteRegEvalCriteria, + deleteRegEvalCriteriaDetails, + insertRegEvalCriteria, + insertRegEvalCriteriaDetails, + selectRegEvalCriteria, + selectRegEvalCriteriaWithDetails, + updateRegEvalCriteria, + updateRegEvalCriteriaDetails, +} from './repository'; +import db from '@/db/db'; +import { filterColumns } from '@/lib/filter-columns'; +import { + regEvalCriteriaView, + type NewRegEvalCriteria, + type NewRegEvalCriteriaDetails, + type RegEvalCriteria, + type RegEvalCriteriaDetails, +} from '@/db/schema'; +import { type GetRegEvalCriteriaSchema } from './validations'; + +// ---------------------------------------------------------------------------------------------------- + +/* FUNCTION FOR GETTING CRITERIA */ +async function getRegEvalCriteria(input: GetRegEvalCriteriaSchema) { + try { + const offset = (input.page - 1) * input.perPage; + const advancedWhere = filterColumns({ + table: regEvalCriteriaView, + filters: input.filters, + joinOperator: input.joinOperator, + }); + + // Filtering + let globalWhere; + if (input.search) { + const s = `%${input.search}%`; + globalWhere = or( + ilike(regEvalCriteriaView.category, s), + ilike(regEvalCriteriaView.item, s), + ilike(regEvalCriteriaView.classification, s), + ); + } + const finalWhere = and(advancedWhere, globalWhere); + + // Sorting + const orderBy = input.sort.length > 0 + ? input.sort.map((item) => { + return item.desc + ? desc(regEvalCriteriaView[item.id]) + : asc(regEvalCriteriaView[item.id]); + }) + : [asc(regEvalCriteriaView.id)]; + + // Getting Data + const { data, total } = await db.transaction(async (tx) => { + const data = await selectRegEvalCriteria(tx, { + where: finalWhere, + orderBy, + offset, + limit: input.perPage, + }); + const total = await countRegEvalCriteria(tx, finalWhere); + + return { data, total }; + }); + const pageCount = Math.ceil(total / input.perPage); + return { data, pageCount }; + } catch (err) { + console.error('Error in Getting Regular Evaluation Criteria: ', err); + return { data: [], pageCount: 0 }; + } +} + +/* FUNCTION FOR GETTING CRITERIA WITH DETAILS */ +async function getRegEvalCriteriaWithDetails(id: number) { + try { + return await db.transaction(async (tx) => { + return await selectRegEvalCriteriaWithDetails(tx, id); + }); + } catch (err) { + console.error('Error in Getting Regular Evaluation Criteria with Details: ', err); + return null; + } +} + +// ---------------------------------------------------------------------------------------------------- + +/* FUNCTION FOR CREATING CRITERIA WITH DETAILS */ +async function createRegEvalCriteriaWithDetails( + criteriaData: NewRegEvalCriteria, + detailList: Omit[], +) { + try { + return await db.transaction(async (tx) => { + const criteria = await insertRegEvalCriteria(tx, criteriaData); + const criteriaId = criteria.id; + const newDetailList = detailList.map((detailItem, index) => ({ + ...detailItem, + criteriaId, + orderIndex: index, + })); + + const criteriaDetails: NewRegEvalCriteriaDetails[] = []; + for (let idx = 0; idx < newDetailList.length; idx += 1) { + criteriaDetails.push(await insertRegEvalCriteriaDetails(tx, newDetailList[idx])); + } + + return { ...criteria, criteriaDetails }; + }); + } catch (error) { + console.error('Error in Creating New Regular Evaluation Criteria with Details: ', error); + throw new Error('Failed to Create New Regular Evaluation Criteria with Details'); + } +} + +// ---------------------------------------------------------------------------------------------------- + +/* FUNCTION FOR MODIFYING CRITERIA WITH DETAILS */ +async function modifyRegEvalCriteriaWithDetails( + id: number, + criteriaData: Partial, + detailList: Partial[], +) { + try { + return await db.transaction(async (tx) => { + const modifiedCriteria = await updateRegEvalCriteria(tx, id, criteriaData); + + console.log('here!'); + console.log(detailList); + + const originCriteria = await getRegEvalCriteriaWithDetails(id); + const originCriteriaDetails = originCriteria?.criteriaDetails || []; + const detailIdList = detailList + .filter(item => item.id !== undefined) + .map(item => item.id); + const toDeleteIdList = originCriteriaDetails.filter( + (item) => !detailIdList.includes(item.id), + ); + + for (const item of toDeleteIdList) { + await deleteRegEvalCriteriaDetails(tx, item.id); + } + + const criteriaDetails = []; + for (let idx = 0; idx < detailList.length; idx += 1) { + const detailItem = detailList[idx]; + const isUpdate = detailItem.id; + const isInsert = !detailItem.id && detailItem.detail; + + if (isUpdate) { + const updatedDetail = await updateRegEvalCriteriaDetails(tx, detailItem.id!, detailItem); + criteriaDetails.push(updatedDetail); + } else if (isInsert) { + const newDetailItem = { + ...detailItem, + criteriaId: id, + detail: detailItem.detail!, + orderIndex: idx, + }; + const insertedDetail = await insertRegEvalCriteriaDetails(tx, newDetailItem); + criteriaDetails.push(insertedDetail); + } + } + + return { ...modifiedCriteria, criteriaDetails }; + }); + } catch (error) { + console.error('Error in Modifying Regular Evaluation Criteria with Details: ', error); + throw new Error('Failed to Modify Regular Evaluation Criteria with Details'); + } +} + +// ---------------------------------------------------------------------------------------------------- + +/* FUNCTION FOR REMOVING CRITERIA WITH DETAILS */ +async function removeRegEvalCriteria(id: number) { + try { + return await db.transaction(async (tx) => { + return await deleteRegEvalCriteria(tx, id); + }); + } catch (err) { + console.error('Error in Removing Regular Evaluation Criteria with Details: ', err); + throw new Error('Failed to Remove Regular Evaluation Criteria with Details'); + } +} + +/* FUNCTION FOR REMOVING CRITERIA DETAILS */ +async function removeRegEvalCriteriaDetails(id: number) { + try { + return await db.transaction(async (tx) => { + return await deleteRegEvalCriteriaDetails(tx, id); + }); + } catch (err) { + console.error('Error in Removing Regular Evaluation Criteria Details: ', err); + throw new Error('Failed to Remove Regular Evaluation Criteria Details'); + } +} + +// ---------------------------------------------------------------------------------------------------- + +/* EXPORT */ +export { + createRegEvalCriteriaWithDetails, + modifyRegEvalCriteriaWithDetails, + getRegEvalCriteria, + getRegEvalCriteriaWithDetails, + removeRegEvalCriteria, + removeRegEvalCriteriaDetails, +}; \ No newline at end of file diff --git a/lib/evaluation-criteria/table/reg-eval-criteria-columns.tsx b/lib/evaluation-criteria/table/reg-eval-criteria-columns.tsx new file mode 100644 index 00000000..7367fabb --- /dev/null +++ b/lib/evaluation-criteria/table/reg-eval-criteria-columns.tsx @@ -0,0 +1,364 @@ +'use client'; + +/* IMPORT */ +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { Checkbox } from '@/components/ui/checkbox'; +import { DataTableColumnHeaderSimple } from '@/components/data-table/data-table-column-simple-header'; +import { Dispatch, SetStateAction } from 'react'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import { PenToolIcon, TrashIcon } from 'lucide-react'; +import { + REG_EVAL_CRITERIA_CATEGORY, + REG_EVAL_CRITERIA_ITEM, + REG_EVAL_CRITERIA_CATEGORY2, + type RegEvalCriteriaView, +} from '@/db/schema'; +import { type ColumnDef } from '@tanstack/react-table'; +import { type DataTableRowAction } from '@/types/table'; + +// ---------------------------------------------------------------------------------------------------- + +/* TYPES */ +interface GetColumnsProps { + setRowAction: Dispatch | null>>, +}; + +// ---------------------------------------------------------------------------------------------------- + +/* FUNCTION FOR GETTING COLUMNS SETTING */ +function getColumns({ setRowAction }: GetColumnsProps): ColumnDef[] { + + // [1] SELECT COLUMN - CHECKBOX + const selectColumn: ColumnDef = { + id: 'select', + header: ({ table }) => ( + table.toggleAllPageRowsSelected(!!value)} + aria-label="select-all" + className="translate-y-0.5" + /> + ), + cell: ({ row }) => ( + row.toggleSelected(!!value)} + aria-label="select-row" + className="translate-y-0.5" + /> + ), + enableSorting: false, + enableHiding: false, + size:40, + }; + + // [2] CRITERIA COLUMNS + const criteriaColumns: ColumnDef[] = [ + { + accessorKey: 'category', + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const value = row.getValue('category'); + const label = REG_EVAL_CRITERIA_CATEGORY.find(item => item.value === value)?.label ?? value; + return ( + + {label} + + ); + }, + enableSorting: true, + enableHiding: false, + meta: { + excelHeader: 'Category', + type: 'select', + }, + }, + { + accessorKey: 'scoreCategory', + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const value = row.getValue('scoreCategory'); + const label = REG_EVAL_CRITERIA_CATEGORY2.find(item => item.value === value)?.label ?? value; + return ( + + {label} + + ); + }, + enableSorting: true, + enableHiding: false, + meta: { + excelHeader: 'Score Category', + type: 'select', + }, + }, + { + accessorKey: 'item', + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const value = row.getValue('item'); + const label = REG_EVAL_CRITERIA_ITEM.find(item => item.value === value)?.label ?? value; + return ( + + {label} + + ); + }, + enableSorting: true, + enableHiding: false, + meta: { + excelHeader: 'Item', + type: 'select', + }, + }, + { + accessorKey: 'classification', + header: ({ column }) => ( + + ), + cell: ({ row }) => ( +
+ {row.getValue('classification')} +
+ ), + enableSorting: true, + enableHiding: false, + meta: { + excelHeader: 'Classification', + type: 'text', + }, + }, + { + accessorKey: 'range', + header: ({ column }) => ( + + ), + cell: ({ row }) => ( +
+ {row.getValue('range') || '-'} +
+ ), + enableSorting: true, + enableHiding: false, + meta: { + excelHeader: 'Range', + type: 'text', + }, + }, + { + accessorKey: 'detail', + header: ({ column }) => ( + + ), + cell: ({ row }) => ( +
+ {row.getValue('detail')} +
+ ), + enableSorting: true, + enableHiding: false, + meta: { + excelHeader: 'Detail', + type: 'text', + }, + }, + ]; + + // [3] SCORE COLUMNS + const scoreEquipColumns: ColumnDef[] = [ + { + accessorKey: 'scoreEquipShip', + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const value = row.getValue('scoreEquipShip'); + const displayValue = typeof value === 'string' + ? parseFloat(parseFloat(value).toFixed(2)).toString() + : '-'; + return ( +
+ {displayValue} +
+ ); + }, + enableSorting: true, + enableHiding: false, + meta: { + excelHeader: 'Equipment-Shipbuilding Score', + group: 'Equipment Score', + type: 'number', + }, + }, + { + accessorKey: 'scoreEquipMarine', + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const value = row.getValue('scoreEquipMarine'); + const displayValue = typeof value === 'string' + ? parseFloat(parseFloat(value).toFixed(2)).toString() + : '-'; + return ( +
+ {displayValue} +
+ ); + }, + enableSorting: true, + enableHiding: false, + meta: { + excelHeader: 'Equipment-Marine Engineering Score', + group: 'Equipment Score', + type: 'number', + }, + }, + ]; + const scoreBulkColumns: ColumnDef[] = [ + { + accessorKey: 'scoreBulkShip', + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const value = row.getValue('scoreBulkShip'); + const displayValue = typeof value === 'string' + ? parseFloat(parseFloat(value).toFixed(2)).toString() + : '-'; + return ( +
+ {displayValue} +
+ ); + }, + enableSorting: true, + enableHiding: false, + meta: { + excelHeader: 'Bulk-Shipbuiling Score', + group: 'Bulk Score', + type: 'number', + }, + }, + { + accessorKey: 'scoreBulkMarine', + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const value = row.getValue('scoreBulkMarine'); + const displayValue = typeof value === 'string' + ? parseFloat(parseFloat(value).toFixed(2)).toString() + : '-'; + return ( +
+ {displayValue} +
+ ); + }, + enableSorting: true, + enableHiding: false, + meta: { + excelHeader: 'Bulk-Marine Engineering Score', + group: 'Bulk Score', + type: 'number', + }, + }, + ]; + + // [4] REMARKS COLUMN + const remarksColumn: ColumnDef = { + accessorKey: 'remarks', + header: ({ column }) => ( + + ), + cell: ({ row }) => ( +
+ {row.getValue('remarks') || '-'} +
+ ), + enableSorting: true, + enableHiding: false, + meta: { + excelHeader: 'Remarks', + type: 'text', + }, + }; + + // [5] ACTIONS COLUMN - DROPDOWN MENU + const actionsColumn: ColumnDef = { + id: 'actions', + header: '작업', + enableHiding: false, + cell: function Cell({ row }) { + return ( + + + + + + setRowAction({ row, type: 'update' })} + > + + Modify Criteria + + setRowAction({ row, type: 'delete' })} + className="text-destructive" + > + + Delete Criteria + + + + ) + }, + size: 80, + }; + + return [ + selectColumn, + ...criteriaColumns, + { + id: 'score', + header: '배점', + columns: [ + { + id: 'scoreEquip', + header: '기자재', + columns: scoreEquipColumns, + }, + { + id: 'scoreBulk', + header: '벌크', + columns: scoreBulkColumns, + }, + ], + }, + remarksColumn, + actionsColumn, + ]; +}; + +// ---------------------------------------------------------------------------------------------------- + +/* EXPORT */ +export default getColumns; \ No newline at end of file diff --git a/lib/evaluation-criteria/table/reg-eval-criteria-delete-dialog.tsx b/lib/evaluation-criteria/table/reg-eval-criteria-delete-dialog.tsx new file mode 100644 index 00000000..aac7db29 --- /dev/null +++ b/lib/evaluation-criteria/table/reg-eval-criteria-delete-dialog.tsx @@ -0,0 +1,147 @@ +'use client'; + +/* IMPORT */ +import { Button } from '@/components/ui/button'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { + getRegEvalCriteriaWithDetails, + removeRegEvalCriteria, +} from '../service'; +import { LoaderCircle } from 'lucide-react'; +import { toast } from 'sonner'; +import { + REG_EVAL_CRITERIA_CATEGORY, + REG_EVAL_CRITERIA_ITEM, + REG_EVAL_CRITERIA_SCORE_CATEGORY, + type RegEvalCriteriaView, + type RegEvalCriteriaWithDetails, +} from '@/db/schema'; +import { useEffect, useState } from 'react'; + +// ---------------------------------------------------------------------------------------------------- + +/* TYPES */ +interface RegEvalCriteriaDeleteDialogProps { + open: boolean, + onOpenChange: (open: boolean) => void, + criteriaViewData: RegEvalCriteriaView, + onSuccess: () => void, +} + +// ---------------------------------------------------------------------------------------------------- + +/* REGULAR EVALUATION CRITERIA DELETE DIALOG COMPONENT */ +function RegEvalCriteriaDeleteDialog(props: RegEvalCriteriaDeleteDialogProps) { + const { open, onOpenChange, criteriaViewData, onSuccess } = props; + const [isLoading, setIsLoading] = useState(false); + const [isDeleting, setIsDeleting] = useState(false); + const [targetData, setTargetData] = useState(); + + useEffect(() => { + const fetchData = async () => { + if (!criteriaViewData?.criteriaId) { + return; + } + setIsLoading(true); + try { + const result = await getRegEvalCriteriaWithDetails(criteriaViewData.criteriaId); + setTargetData(result); + } catch (error) { + console.error('Error in Loading Target Data for Deletion: ', error); + } finally { + setIsLoading(false); + } + } + fetchData(); + }, [criteriaViewData.criteriaId]); + + const handleDelete = async () => { + if (!criteriaViewData || !criteriaViewData.criteriaId) { + return; + } + + try { + setIsDeleting(true); + await removeRegEvalCriteria(criteriaViewData.criteriaId); + toast.success('평가 기준이 삭제되었습니다.'); + onSuccess(); + } catch (error) { + console.error('Error in Deleting Regular Evaluation Criteria: ', error); + toast.error( + error instanceof Error ? error.message : '삭제 중 오류가 발생했습니다.' + ); + } finally { + setIsDeleting(false); + } + } + + if (!criteriaViewData) { + return null; + } + + return ( + + + {isLoading ? ( +
+ +

Loading...

+
+ ) : ( + <> + + 협력업체 평가 기준 삭제 + + 정말로 이 협력업체 평가 기준을 삭제하시겠습니까? +
+
+ 삭제될 평가 기준: +
+ • 평가부문: {REG_EVAL_CRITERIA_CATEGORY.find((c) => c.value === criteriaViewData.category)?.label ?? '-'} +
+ • 점수구분: {REG_EVAL_CRITERIA_SCORE_CATEGORY.find((c) => c.value === criteriaViewData.scoreCategory)?.label ?? '-'} +
+ • 항목: {REG_EVAL_CRITERIA_ITEM.find((c) => c.value === criteriaViewData.item)?.label ?? '-'} +
+ • 구분: {criteriaViewData.classification || '-'} +
+ • 범위: {criteriaViewData.range || '-'} +
+
+ 이 작업은 되돌릴 수 없으며, 평가기준과 그에 속한 {targetData?.criteriaDetails.length}개의 평가항목도 함께 삭제됩니다. +
+
+ + + + + + )} +
+
+ ) +} + +// ---------------------------------------------------------------------------------------------------- + +/* EXPORT */ +export default RegEvalCriteriaDeleteDialog; \ No newline at end of file diff --git a/lib/evaluation-criteria/table/reg-eval-criteria-form-sheet.tsx b/lib/evaluation-criteria/table/reg-eval-criteria-form-sheet.tsx new file mode 100644 index 00000000..d33e7d29 --- /dev/null +++ b/lib/evaluation-criteria/table/reg-eval-criteria-form-sheet.tsx @@ -0,0 +1,586 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +'use client'; + +/* IMPORT */ +import { Button } from '@/components/ui/button'; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from '@/components/ui/card'; +import { + createRegEvalCriteriaWithDetails, + getRegEvalCriteriaWithDetails, + modifyRegEvalCriteriaWithDetails, +} from '../service'; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; +import { Plus, Trash2 } from 'lucide-react'; +import { + REG_EVAL_CRITERIA_CATEGORY, + REG_EVAL_CRITERIA_CATEGORY2, + REG_EVAL_CRITERIA_ITEM, + type RegEvalCriteriaDetails, + type RegEvalCriteriaView, +} from '@/db/schema'; +import { ScrollArea } from '@/components/ui/scroll-area'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from '@/components/ui/select'; +import { + Sheet, + SheetContent, + SheetDescription, + SheetHeader, + SheetTitle, +} from '@/components/ui/sheet'; +import { Textarea } from '@/components/ui/textarea'; +import { toast } from 'sonner'; +import { useForm, useFieldArray } from 'react-hook-form'; +import { useEffect, useTransition } from 'react'; +import { z } from 'zod'; +import { zodResolver } from '@hookform/resolvers/zod'; + +// ---------------------------------------------------------------------------------------------------- + +/* TYPES */ +const regEvalCriteriaFormSchema = z.object({ + category: z.string().min(1, '평가부문은 필수 항목입니다.'), + scoreCategory: z.string().min(1, '점수구분은 필수 항목입니다.'), + item: z.string().min(1, '항목은 필수 항목입니다.'), + classification: z.string().min(1, '구분은 필수 항목입니다.'), + range: z.string().nullable().optional(), + remarks: z.string().nullable().optional(), + criteriaDetails: z.array( + z.object({ + id: z.number().optional(), + detail: z.string().min(1, '평가내용은 필수 항목입니다.'), + scoreEquipShip: z.coerce.number().nullable().optional(), + scoreEquipMarine: z.coerce.number().nullable().optional(), + scoreBulkShip: z.coerce.number().nullable().optional(), + scoreBulkMarine: z.coerce.number().nullable().optional(), + }) + ).min(1, '최소 1개의 평가 내용이 필요합니다.'), +}); +type RegEvalCriteriaFormData = z.infer; +interface CriteriaDetailFormProps { + index: number + form: any + onRemove: () => void + canRemove: boolean + disabled?: boolean +} +interface RegEvalCriteriaFormSheetProps { + open: boolean, + onOpenChange: (open: boolean) => void, + criteriaViewData: RegEvalCriteriaView | null, + onSuccess: () => void, +}; + +// ---------------------------------------------------------------------------------------------------- + +/* CRITERIA DETAIL FORM COPONENT */ +function CriteriaDetailForm({ + index, + form, + onRemove, + canRemove, + disabled = false, +}: CriteriaDetailFormProps) { + + return ( + + +
+ Detail Item - {index + 1} + {canRemove && ( + + )} +
+
+ + ( + + )} + /> + ( + + 평가내용 + +