summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/[lng]/evcp/(evcp)/approval/line/page.tsx3
-rw-r--r--components/common/user/user-tree.tsx0
-rw-r--r--lib/approval-line/service.ts17
-rw-r--r--lib/approval-line/table/approval-line-table-columns.tsx15
-rw-r--r--lib/approval-line/table/approval-line-table.tsx5
-rw-r--r--lib/approval-line/table/create-approval-line-sheet.tsx17
-rw-r--r--lib/approval-line/table/update-approval-line-sheet.tsx17
-rw-r--r--lib/approval-line/validations.ts1
-rw-r--r--lib/approval-template/service.ts11
-rw-r--r--lib/revalidate.ts32
10 files changed, 117 insertions, 1 deletions
diff --git a/app/[lng]/evcp/(evcp)/approval/line/page.tsx b/app/[lng]/evcp/(evcp)/approval/line/page.tsx
index 435d1071..38b43680 100644
--- a/app/[lng]/evcp/(evcp)/approval/line/page.tsx
+++ b/app/[lng]/evcp/(evcp)/approval/line/page.tsx
@@ -46,12 +46,13 @@ export default async function ApprovalLinePage({ searchParams }: PageProps) {
<React.Suspense
fallback={
<DataTableSkeleton
- columnCount={6}
+ columnCount={7}
searchableColumnCount={1}
filterableColumnCount={2}
cellWidths={[
'10rem',
'20rem',
+ '12rem',
'30rem',
'12rem',
'12rem',
diff --git a/components/common/user/user-tree.tsx b/components/common/user/user-tree.tsx
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/components/common/user/user-tree.tsx
diff --git a/lib/approval-line/service.ts b/lib/approval-line/service.ts
index 3000e25f..f3c3fb95 100644
--- a/lib/approval-line/service.ts
+++ b/lib/approval-line/service.ts
@@ -1,6 +1,7 @@
'use server';
import db from '@/db/db';
+import { revalidateI18nPaths } from '@/lib/revalidate';
import {
and,
asc,
@@ -21,6 +22,14 @@ import { filterColumns } from '@/lib/filter-columns';
// ---------------------------------------------
export type ApprovalLine = typeof approvalLines.$inferSelect;
+// ---------------------------------------------
+// Revalidation helpers
+// ---------------------------------------------
+
+async function revalidateApprovalLinesPaths() {
+ await revalidateI18nPaths('/evcp/approval/line');
+}
+
export interface ApprovalLineWithUsage extends ApprovalLine {
templateCount: number; // 사용 중인 템플릿 수
@@ -193,6 +202,7 @@ export async function getApprovalLine(id: string): Promise<ApprovalLine | null>
interface CreateInput {
name: string;
description?: string;
+ category?: string | null;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
aplns: any[]; // 결재선 구성 (JSON)
createdBy: number;
@@ -215,12 +225,14 @@ export async function createApprovalLine(data: CreateInput): Promise<ApprovalLin
.values({
name: data.name,
description: data.description,
+ category: data.category ?? null,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
aplns: data.aplns as any,
createdBy: data.createdBy,
})
.returning();
+ await revalidateApprovalLinesPaths();
return newLine;
}
@@ -230,6 +242,7 @@ export async function createApprovalLine(data: CreateInput): Promise<ApprovalLin
interface UpdateInput {
name?: string;
description?: string;
+ category?: string | null;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
aplns?: any[]; // 결재선 구성 (JSON)
updatedBy: number;
@@ -263,6 +276,7 @@ export async function updateApprovalLine(id: string, data: UpdateInput): Promise
.set({
name: data.name ?? existing.name,
description: data.description ?? existing.description,
+ category: data.category ?? existing.category,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
aplns: (data.aplns as any) ?? (existing.aplns as any),
updatedAt: new Date(),
@@ -271,6 +285,7 @@ export async function updateApprovalLine(id: string, data: UpdateInput): Promise
const result = await getApprovalLine(id);
if (!result) throw new Error('업데이트된 결재선을 조회할 수 없습니다.');
+ await revalidateApprovalLinesPaths();
return result;
}
@@ -305,6 +320,7 @@ export async function duplicateApprovalLine(
const duplicated = await createApprovalLine({
name: newName,
description: existing.description ? `${existing.description} (복사본)` : undefined,
+ category: existing.category ?? null,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
aplns: existing.aplns as any,
createdBy,
@@ -322,6 +338,7 @@ export async function duplicateApprovalLine(
export async function deleteApprovalLine(id: string): Promise<{ success: boolean; error?: string }> {
try {
await db.delete(approvalLines).where(eq(approvalLines.id, id));
+ await revalidateApprovalLinesPaths();
return { success: true };
} catch (error) {
return {
diff --git a/lib/approval-line/table/approval-line-table-columns.tsx b/lib/approval-line/table/approval-line-table-columns.tsx
index 5b35b92c..a9cf58e1 100644
--- a/lib/approval-line/table/approval-line-table-columns.tsx
+++ b/lib/approval-line/table/approval-line-table-columns.tsx
@@ -68,6 +68,21 @@ export function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<Approva
},
},
{
+ accessorKey: "category",
+ header: ({ column }) => (
+ <DataTableColumnHeader column={column} title="카테고리" />
+ ),
+ cell: ({ row }) => {
+ return (
+ <div className="flex space-x-2">
+ <span className="max-w-[200px] truncate">
+ {row.getValue("category") || "-"}
+ </span>
+ </div>
+ )
+ },
+ },
+ {
accessorKey: "description",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="설명" />
diff --git a/lib/approval-line/table/approval-line-table.tsx b/lib/approval-line/table/approval-line-table.tsx
index 21b9972c..61cddf8b 100644
--- a/lib/approval-line/table/approval-line-table.tsx
+++ b/lib/approval-line/table/approval-line-table.tsx
@@ -47,6 +47,11 @@ export function ApprovalLineTable({ promises }: ApprovalLineTableProps) {
type: 'text',
},
{
+ id: 'category',
+ label: '카테고리',
+ type: 'text',
+ },
+ {
id: 'description',
label: '설명',
type: 'text',
diff --git a/lib/approval-line/table/create-approval-line-sheet.tsx b/lib/approval-line/table/create-approval-line-sheet.tsx
index fdc8cc64..c19d11ab 100644
--- a/lib/approval-line/table/create-approval-line-sheet.tsx
+++ b/lib/approval-line/table/create-approval-line-sheet.tsx
@@ -20,6 +20,7 @@ import {
FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Textarea } from "@/components/ui/textarea"
import { Separator } from "@/components/ui/separator"
import { toast } from "sonner"
@@ -46,6 +47,7 @@ export function CreateApprovalLineSheet({ open, onOpenChange }: CreateApprovalLi
resolver: zodResolver(ApprovalLineSchema),
defaultValues: {
name: "",
+ category: "",
description: "",
aplns: [
// 기안자는 항상 첫 번째로 고정 (플레이스홀더)
@@ -104,6 +106,7 @@ export function CreateApprovalLineSheet({ open, onOpenChange }: CreateApprovalLi
await createApprovalLine({
name: data.name,
+ category: data.category || undefined,
description: data.description,
aplns: data.aplns,
createdBy: Number(session.user.id),
@@ -150,6 +153,20 @@ export function CreateApprovalLineSheet({ open, onOpenChange }: CreateApprovalLi
<FormField
control={form.control}
+ name="category"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>카테고리</FormLabel>
+ <FormControl>
+ <Input placeholder="카테고리를 입력하세요" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ <FormField
+ control={form.control}
name="description"
render={({ field }) => (
<FormItem>
diff --git a/lib/approval-line/table/update-approval-line-sheet.tsx b/lib/approval-line/table/update-approval-line-sheet.tsx
index efc720de..9153eb38 100644
--- a/lib/approval-line/table/update-approval-line-sheet.tsx
+++ b/lib/approval-line/table/update-approval-line-sheet.tsx
@@ -61,6 +61,7 @@ export function UpdateApprovalLineSheet({ open, onOpenChange, line }: UpdateAppr
resolver: zodResolver(ApprovalLineSchema),
defaultValues: {
name: "",
+ category: "",
description: "",
aplns: [],
},
@@ -94,6 +95,7 @@ export function UpdateApprovalLineSheet({ open, onOpenChange, line }: UpdateAppr
form.reset({
name: line.name,
+ category: line.category || "",
description: line.description || "",
aplns: nextAplns as ApprovalLineFormData["aplns"],
});
@@ -140,6 +142,7 @@ export function UpdateApprovalLineSheet({ open, onOpenChange, line }: UpdateAppr
try {
await updateApprovalLine(line.id, {
name: data.name,
+ category: data.category || null,
description: data.description,
aplns: data.aplns,
updatedBy: Number(session.user.id),
@@ -190,6 +193,20 @@ export function UpdateApprovalLineSheet({ open, onOpenChange, line }: UpdateAppr
<FormField
control={form.control}
+ name="category"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>카테고리</FormLabel>
+ <FormControl>
+ <Input placeholder="카테고리를 입력하세요" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ <FormField
+ control={form.control}
name="description"
render={({ field }) => (
<FormItem>
diff --git a/lib/approval-line/validations.ts b/lib/approval-line/validations.ts
index 4f55454a..141c12e6 100644
--- a/lib/approval-line/validations.ts
+++ b/lib/approval-line/validations.ts
@@ -7,6 +7,7 @@ export const SearchParamsApprovalLineCache = SearchParamsApprovalTemplateCache;
// 결재선 생성/수정 스키마
export const ApprovalLineSchema = z.object({
name: z.string().min(1, '결재선 이름은 필수입니다'),
+ category: z.string().optional(),
description: z.string().optional(),
aplns: z.array(z.object({
id: z.string(),
diff --git a/lib/approval-template/service.ts b/lib/approval-template/service.ts
index 5dc989d9..cb687c4f 100644
--- a/lib/approval-template/service.ts
+++ b/lib/approval-template/service.ts
@@ -1,6 +1,7 @@
'use server';
import db from '@/db/db';
+import { revalidateI18nPaths } from '@/lib/revalidate';
import {
and,
asc,
@@ -28,6 +29,13 @@ export type ApprovalTemplateVariable =
typeof approvalTemplateVariables.$inferSelect;
export type ApprovalTemplateHistory =
typeof approvalTemplateHistory.$inferSelect;
+// ---------------------------------------------
+// Revalidation helpers
+// ---------------------------------------------
+async function revalidateApprovalTemplatesPaths() {
+ await revalidateI18nPaths('/evcp/approval/template');
+}
+
export interface ApprovalTemplateWithVariables extends ApprovalTemplate {
variables: ApprovalTemplateVariable[];
@@ -209,6 +217,7 @@ export async function createApprovalTemplate(data: CreateInput): Promise<Approva
const result = await getApprovalTemplate(newTemplate.id);
if (!result) throw new Error('생성된 템플릿을 조회할 수 없습니다.');
+ await revalidateApprovalTemplatesPaths();
return result;
}
@@ -262,6 +271,7 @@ export async function updateApprovalTemplate(id: string, data: UpdateInput): Pro
const result = await getApprovalTemplate(id);
if (!result) throw new Error('업데이트된 템플릿을 조회할 수 없습니다.');
+ await revalidateApprovalTemplatesPaths();
return result;
}
@@ -320,6 +330,7 @@ export async function duplicateApprovalTemplate(
export async function deleteApprovalTemplate(id: string): Promise<{ success: boolean; error?: string }> {
try {
await db.delete(approvalTemplates).where(eq(approvalTemplates.id, id));
+ await revalidateApprovalTemplatesPaths();
return { success: true };
} catch (error) {
return {
diff --git a/lib/revalidate.ts b/lib/revalidate.ts
new file mode 100644
index 00000000..08c572ee
--- /dev/null
+++ b/lib/revalidate.ts
@@ -0,0 +1,32 @@
+"use server"
+
+import { revalidatePath } from "next/cache"
+import { languages } from "@/i18n/settings"
+
+/**
+ * 지정된 base 경로에 대해 지원 언어별로 revalidatePath를 호출합니다.
+ * 예: basePath가 "/evcp/approval/line" 이면
+ * - /ko/evcp/approval/line
+ * - /en/evcp/approval/line
+ * 등의 경로를 무효화합니다.
+ */
+export async function revalidateI18nPaths(basePath: string): Promise<void> {
+ for (const lng of languages) {
+ try {
+ revalidatePath(`/${lng}${basePath}`)
+ } catch {
+ // ignore
+ }
+ }
+}
+
+/**
+ * 여러 base 경로를 한 번에 무효화합니다.
+ */
+export async function revalidateI18nPathsMany(basePaths: string[]): Promise<void> {
+ for (const basePath of basePaths) {
+ await revalidateI18nPaths(basePath)
+ }
+}
+
+