summaryrefslogtreecommitdiff
path: root/lib/gtc-contract
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-07-25 07:51:15 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-07-25 07:51:15 +0000
commit2650b7c0bb0ea12b68a58c0439f72d61df04b2f1 (patch)
tree17156183fd74b69d78178065388ac61a18ac07b4 /lib/gtc-contract
parentd32acea05915bd6c1ed4b95e56c41ef9204347bc (diff)
(대표님) 정기평가 대상, 미들웨어 수정, nextauth 토큰 처리 개선, GTC 등
(최겸) 기술영업
Diffstat (limited to 'lib/gtc-contract')
-rw-r--r--lib/gtc-contract/service.ts105
-rw-r--r--lib/gtc-contract/status/create-gtc-document-dialog.tsx18
-rw-r--r--lib/gtc-contract/status/delete-gtc-documents-dialog.tsx8
-rw-r--r--lib/gtc-contract/status/gtc-contract-table.tsx8
-rw-r--r--lib/gtc-contract/status/gtc-documents-table-columns.tsx4
-rw-r--r--lib/gtc-contract/status/gtc-documents-table-floating-bar.tsx4
-rw-r--r--lib/gtc-contract/status/gtc-documents-table-toolbar-actions.tsx4
-rw-r--r--lib/gtc-contract/validations.ts62
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({