diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-07-25 07:51:15 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-07-25 07:51:15 +0000 |
| commit | 2650b7c0bb0ea12b68a58c0439f72d61df04b2f1 (patch) | |
| tree | 17156183fd74b69d78178065388ac61a18ac07b4 /lib/gtc-contract | |
| parent | d32acea05915bd6c1ed4b95e56c41ef9204347bc (diff) | |
(대표님) 정기평가 대상, 미들웨어 수정, nextauth 토큰 처리 개선, GTC 등
(최겸) 기술영업
Diffstat (limited to 'lib/gtc-contract')
| -rw-r--r-- | lib/gtc-contract/service.ts | 105 | ||||
| -rw-r--r-- | lib/gtc-contract/status/create-gtc-document-dialog.tsx | 18 | ||||
| -rw-r--r-- | lib/gtc-contract/status/delete-gtc-documents-dialog.tsx | 8 | ||||
| -rw-r--r-- | lib/gtc-contract/status/gtc-contract-table.tsx | 8 | ||||
| -rw-r--r-- | lib/gtc-contract/status/gtc-documents-table-columns.tsx | 4 | ||||
| -rw-r--r-- | lib/gtc-contract/status/gtc-documents-table-floating-bar.tsx | 4 | ||||
| -rw-r--r-- | lib/gtc-contract/status/gtc-documents-table-toolbar-actions.tsx | 4 | ||||
| -rw-r--r-- | lib/gtc-contract/validations.ts | 62 |
8 files changed, 91 insertions, 122 deletions
diff --git a/lib/gtc-contract/service.ts b/lib/gtc-contract/service.ts index 61e69995..23cdd422 100644 --- a/lib/gtc-contract/service.ts +++ b/lib/gtc-contract/service.ts @@ -1,7 +1,9 @@ +'use server' + import { unstable_cache } from "next/cache" import { and, desc, asc, eq, or, ilike, count, max } from "drizzle-orm" import db from "@/db/db" -import { gtcDocuments, type GtcDocument, type GtcDocumentWithRelations } from "@/db/schema/gtc" +import { gtcDocuments, gtcDocumentsView, type GtcDocument, type GtcDocumentWithRelations } from "@/db/schema/gtc" import { projects } from "@/db/schema/projects" import { users } from "@/db/schema/users" import { filterColumns } from "@/lib/filter-columns" @@ -20,44 +22,7 @@ export async function checkProjectExists(projectId: number): Promise<boolean> { return result.length > 0 } -/** - * GTC 문서 관련 뷰/조인 쿼리를 위한 기본 select - */ -function selectGtcDocumentsWithRelations() { - return db - .select({ - id: gtcDocuments.id, - type: gtcDocuments.type, - projectId: gtcDocuments.projectId, - revision: gtcDocuments.revision, - createdAt: gtcDocuments.createdAt, - createdById: gtcDocuments.createdById, - updatedAt: gtcDocuments.updatedAt, - updatedById: gtcDocuments.updatedById, - editReason: gtcDocuments.editReason, - isActive: gtcDocuments.isActive, - // 관계 데이터 - project: { - id: projects.id, - code: projects.code, - name: projects.name, - }, - createdBy: { - id: users.id, - name: users.name, - email: users.email, - }, - updatedBy: { - id: users.id, - name: users.name, - email: users.email, - }, - }) - .from(gtcDocuments) - .leftJoin(projects, eq(gtcDocuments.projectId, projects.id)) - .leftJoin(users, eq(gtcDocuments.createdById, users.id)) - .leftJoin(users, eq(gtcDocuments.updatedById, users.id)) -} + /** * GTC 문서 개수 조회 @@ -82,7 +47,7 @@ export async function getGtcDocuments(input: GetGtcDocumentsSchema) { // (1) advancedWhere - 고급 필터 const advancedWhere = filterColumns({ - table: gtcDocuments, + table: gtcDocumentsView, filters: input.filters, joinOperator: input.joinOperator, }) @@ -92,49 +57,37 @@ export async function getGtcDocuments(input: GetGtcDocumentsSchema) { if (input.search) { const s = `%${input.search}%` globalWhere = or( - ilike(gtcDocuments.editReason, s), + ilike(gtcDocumentsView.editReason, s), ilike(projects.name, s), ilike(projects.code, s) ) } - // (3) 기본 필터들 - const basicFilters = [] - - if (input.type && input.type !== "") { - basicFilters.push(eq(gtcDocuments.type, input.type)) - } - - if (input.projectId && input.projectId > 0) { - basicFilters.push(eq(gtcDocuments.projectId, input.projectId)) - } - - // 활성 문서만 조회 (기본값) - basicFilters.push(eq(gtcDocuments.isActive, true)) // (4) 최종 where 조건 const finalWhere = and( advancedWhere, globalWhere, - ...basicFilters ) // (5) 정렬 const orderBy = input.sort.length > 0 ? input.sort.map((item) => { - const column = gtcDocuments[item.id as keyof typeof gtcDocuments] + const column = gtcDocumentsView[item.id as keyof typeof gtcDocumentsView] return item.desc ? desc(column) : asc(column) }) - : [desc(gtcDocuments.updatedAt)] + : [desc(gtcDocumentsView.updatedAt)] // (6) 데이터 조회 const { data, total } = await db.transaction(async (tx) => { - const data = await selectGtcDocumentsWithRelations() - .where(finalWhere) - .orderBy(...orderBy) - .offset(offset) - .limit(input.perPage) + const data =await db + .select() + .from(gtcDocumentsView) + .where(finalWhere) + .orderBy(...orderBy) + .limit(input.perPage) + .offset(offset); const total = await countGtcDocuments(tx, finalWhere) return { data, total } @@ -155,17 +108,27 @@ export async function getGtcDocuments(input: GetGtcDocumentsSchema) { )() } -/** - * 특정 GTC 문서 조회 - */ -export async function getGtcDocumentById(id: number): Promise<GtcDocumentWithRelations | null> { - const result = await selectGtcDocumentsWithRelations() - .where(eq(gtcDocuments.id, id)) - .limit(1) +// 성공한 ID들을 반환하는 버전 +export async function deleteGtcDocuments( + ids: number[], + updatedById: number +): Promise<number[]> { + if (ids.length === 0) { + return []; + } - return result[0] || null -} + const updated = await db + .update(gtcDocuments) + .set({ + isActive: false, + updatedById, + updatedAt: new Date(), + }) + .where(inArray(gtcDocuments.id, ids)) + .returning({ id: gtcDocuments.id }); + return updated.map(doc => doc.id); +} /** * 다음 리비전 번호 조회 */ diff --git a/lib/gtc-contract/status/create-gtc-document-dialog.tsx b/lib/gtc-contract/status/create-gtc-document-dialog.tsx index 6791adfa..98cd249f 100644 --- a/lib/gtc-contract/status/create-gtc-document-dialog.tsx +++ b/lib/gtc-contract/status/create-gtc-document-dialog.tsx @@ -42,11 +42,18 @@ import { toast } from "sonner" import { createGtcDocumentSchema, type CreateGtcDocumentSchema } from "@/lib/gtc-contract/validations" import { createGtcDocument, getProjectsForSelect } from "@/lib/gtc-contract/service" import { type Project } from "@/db/schema/projects" +import { useSession } from "next-auth/react" export function CreateGtcDocumentDialog() { const [open, setOpen] = React.useState(false) const [projects, setProjects] = React.useState<Project[]>([]) const [isCreatePending, startCreateTransition] = React.useTransition() + const { data: session } = useSession() + + const currentUserId =React.useMemo(() => { + return session?.user?.id ? Number(session.user.id) : null; + }, [session]); + React.useEffect(() => { if (open) { @@ -70,8 +77,17 @@ export function CreateGtcDocumentDialog() { async function onSubmit(data: CreateGtcDocumentSchema) { startCreateTransition(async () => { + + if (!currentUserId) { + toast.error("로그인이 필요합니다") + return + } + try { - const result = await createGtcDocument(data) + const result = await createGtcDocument({ + ...data, + createdById: currentUserId + }) if (result.error) { toast.error(`에러: ${result.error}`) diff --git a/lib/gtc-contract/status/delete-gtc-documents-dialog.tsx b/lib/gtc-contract/status/delete-gtc-documents-dialog.tsx index 5779a2b6..50c8d3f4 100644 --- a/lib/gtc-contract/status/delete-gtc-documents-dialog.tsx +++ b/lib/gtc-contract/status/delete-gtc-documents-dialog.tsx @@ -29,6 +29,7 @@ import { } from "@/components/ui/drawer" import { deleteGtcDocuments } from "@/lib/gtc-contract/service" +import { useSession } from "next-auth/react" import { type GtcDocumentWithRelations } from "@/db/schema/gtc" interface DeleteGtcDocumentsDialogProps @@ -46,11 +47,18 @@ export function DeleteGtcDocumentsDialog({ }: DeleteGtcDocumentsDialogProps) { const [isDeletePending, startDeleteTransition] = React.useTransition() const isDesktop = useMediaQuery("(min-width: 640px)") + const { data: session } = useSession() function onDelete() { + if (!session?.user?.id) { + toast.error("로그인이 필요합니다.") + return + } + startDeleteTransition(async () => { const { error } = await deleteGtcDocuments({ ids: gtcDocuments.map((doc) => doc.id), + updatedById: Number(session.user.id) }) if (error) { diff --git a/lib/gtc-contract/status/gtc-contract-table.tsx b/lib/gtc-contract/status/gtc-contract-table.tsx index dd04fbc9..0fb637b6 100644 --- a/lib/gtc-contract/status/gtc-contract-table.tsx +++ b/lib/gtc-contract/status/gtc-contract-table.tsx @@ -26,6 +26,7 @@ import { GtcDocumentsTableFloatingBar } from "./gtc-documents-table-floating-bar import { UpdateGtcDocumentSheet } from "./update-gtc-document-sheet" import { CreateGtcDocumentDialog } from "./create-gtc-document-dialog" import { CreateNewRevisionDialog } from "./create-new-revision-dialog" +import { useRouter } from "next/navigation" interface GtcDocumentsTableProps { promises: Promise< @@ -39,13 +40,14 @@ interface GtcDocumentsTableProps { export function GtcDocumentsTable({ promises }: GtcDocumentsTableProps) { const [{ data, pageCount }, projects, users] = React.use(promises) + const router = useRouter() const [rowAction, setRowAction] = React.useState<DataTableRowAction<GtcDocumentWithRelations> | null>(null) const columns = React.useMemo( - () => getColumns({ setRowAction }), - [setRowAction] + () => getColumns({ setRowAction , router}), + [setRowAction, router] ) /** @@ -167,7 +169,7 @@ export function GtcDocumentsTable({ promises }: GtcDocumentsTableProps) { originalDocument={rowAction?.row.original ?? null} /> - <CreateGtcDocumentDialog /> + {/* <CreateGtcDocumentDialog /> */} </> ) }
\ No newline at end of file diff --git a/lib/gtc-contract/status/gtc-documents-table-columns.tsx b/lib/gtc-contract/status/gtc-documents-table-columns.tsx index 2d5f08b9..f6eb81d0 100644 --- a/lib/gtc-contract/status/gtc-documents-table-columns.tsx +++ b/lib/gtc-contract/status/gtc-documents-table-columns.tsx @@ -20,7 +20,6 @@ import { import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header" import { type GtcDocumentWithRelations } from "@/db/schema/gtc" -import { useRouter } from "next/navigation" interface GetColumnsProps { setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<GtcDocumentWithRelations> | null>> @@ -29,8 +28,7 @@ interface GetColumnsProps { /** * GTC Documents 테이블 컬럼 정의 */ -export function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<GtcDocumentWithRelations>[] { - const router = useRouter() +export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef<GtcDocumentWithRelations>[] { // ---------------------------------------------------------------- // 1) select 컬럼 (체크박스) diff --git a/lib/gtc-contract/status/gtc-documents-table-floating-bar.tsx b/lib/gtc-contract/status/gtc-documents-table-floating-bar.tsx index a9139ed2..8fac597e 100644 --- a/lib/gtc-contract/status/gtc-documents-table-floating-bar.tsx +++ b/lib/gtc-contract/status/gtc-documents-table-floating-bar.tsx @@ -8,9 +8,9 @@ import { Button } from "@/components/ui/button" import { Separator } from "@/components/ui/separator" import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip" -import { exportTableToCSV } from "@/lib/export" import { type GtcDocumentWithRelations } from "@/db/schema/gtc" import { DeleteGtcDocumentsDialog } from "./delete-gtc-documents-dialog" +import { exportTableToExcel } from "@/lib/export" interface GtcDocumentsTableFloatingBarProps { table: Table<GtcDocumentWithRelations> @@ -68,7 +68,7 @@ export function GtcDocumentsTableFloatingBar({ variant="secondary" size="sm" onClick={() => - exportTableToCSV(table, { + exportTableToExcel(table, { filename: "gtc-documents", excludeColumns: ["select", "actions"], }) diff --git a/lib/gtc-contract/status/gtc-documents-table-toolbar-actions.tsx b/lib/gtc-contract/status/gtc-documents-table-toolbar-actions.tsx index cb52b2ed..90f2f8a8 100644 --- a/lib/gtc-contract/status/gtc-documents-table-toolbar-actions.tsx +++ b/lib/gtc-contract/status/gtc-documents-table-toolbar-actions.tsx @@ -3,11 +3,11 @@ import { type Table } from "@tanstack/react-table" import { Download } from "lucide-react" -import { exportTableToCSV } from "@/lib/export" import { Button } from "@/components/ui/button" import { type GtcDocumentWithRelations } from "@/db/schema/gtc" import { CreateGtcDocumentDialog } from "./create-gtc-document-dialog" +import { exportTableToExcel } from "@/lib/export" interface GtcDocumentsTableToolbarActionsProps { table: Table<GtcDocumentWithRelations> @@ -23,7 +23,7 @@ export function GtcDocumentsTableToolbarActions({ variant="outline" size="sm" onClick={() => - exportTableToCSV(table, { + exportTableToExcel(table, { filename: "gtc-documents", excludeColumns: ["select", "actions"], }) diff --git a/lib/gtc-contract/validations.ts b/lib/gtc-contract/validations.ts index b79a8b08..671e25b7 100644 --- a/lib/gtc-contract/validations.ts +++ b/lib/gtc-contract/validations.ts @@ -8,7 +8,6 @@ import { } from "nuqs/server" import * as z from "zod" import { getFiltersStateParser, getSortingStateParser } from "@/lib/parsers" -import { checkProjectExists } from "./service" export const searchParamsCache = createSearchParamsCache({ flags: parseAsArrayOf(z.enum(["advancedTable", "floatingBar"])).withDefault( @@ -30,47 +29,30 @@ export const searchParamsCache = createSearchParamsCache({ export const createGtcDocumentSchema = z.object({ type: z.enum(["standard", "project"]), - projectId: z - .number() - .nullable() - .optional() - .refine( - async (projectId, ctx) => { - // 프로젝트 타입인 경우 projectId 필수 - if (ctx.parent.type === "project" && !projectId) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: "Project is required for project type GTC", - }) - return false - } - - // 표준 타입인 경우 projectId null이어야 함 - if (ctx.parent.type === "standard" && projectId) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: "Project should not be set for standard type GTC", - }) - return false - } - - // 프로젝트 ID가 유효한지 검사 - if (projectId) { - const exists = await checkProjectExists(projectId) - if (!exists) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: "Invalid project ID", - }) - return false - } - } - - return true - } - ), + projectId: z.number().nullable().optional(), revision: z.number().min(0).default(0), editReason: z.string().optional(), +}).superRefine(async (data, ctx) => { + // 프로젝트 타입인 경우 projectId 필수 + if (data.type === "project" && !data.projectId) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Project is required for project type GTC", + path: ["projectId"], + }) + return + } + + // 표준 타입인 경우 projectId null이어야 함 + if (data.type === "standard" && data.projectId) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Project should not be set for standard type GTC", + path: ["projectId"], + }) + return + } + }) export const updateGtcDocumentSchema = z.object({ |
