Files
safe-project/src/actions/checklists.ts
Hyungi Ahn 2a9968fa7f feat: TK 안전관리 플랫폼 초기 구현
위험성평가, 안전 RAG Q&A, 안전점검 체크리스트를 통합한
안전관리자 전용 웹 플랫폼 전체 구현.

- Next.js 15 (App Router) + TypeScript + Tailwind + shadcn/ui
- Drizzle ORM + PostgreSQL 16 (12개 테이블)
- 위험성평가 CRUD + 5x5 위험성 매트릭스 + 인쇄 내보내기
- 체크리스트 템플릿/점검/NCR 추적
- RAG 문서 파이프라인 (Tika + bge-m3 + Qdrant)
- SSE 스트리밍 RAG 채팅 (qwen3.5:35b-a3b)
- AI 어시스트 (위험요인 추천, 감소대책, 점검항목 생성)
- 대시보드 통계/차트 (recharts)
- 단일 사용자 인증 (HMAC 쿠키 세션)
- 다크모드 지원
- Docker 멀티스테이지 빌드 (standalone)
- 프로젝트 가이드 문서 (docs/)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 12:33:55 +09:00

225 lines
5.6 KiB
TypeScript

"use server";
import { db } from "@/lib/db";
import {
checklistTemplates,
checklistTemplateItems,
inspections,
inspectionResults,
nonConformances,
} from "@/lib/db/schema";
import { eq, desc, asc } from "drizzle-orm";
import { revalidatePath } from "next/cache";
// ─── Templates ───
export async function getTemplates() {
return db.query.checklistTemplates.findMany({
orderBy: [desc(checklistTemplates.createdAt)],
with: { items: { orderBy: [asc(checklistTemplateItems.sortOrder)] } },
});
}
export async function getTemplate(id: string) {
return db.query.checklistTemplates.findFirst({
where: eq(checklistTemplates.id, id),
with: { items: { orderBy: [asc(checklistTemplateItems.sortOrder)] } },
});
}
export async function createTemplate(data: {
name: string;
type: "daily" | "regular" | "special" | "equipment";
description?: string;
}) {
const [result] = await db
.insert(checklistTemplates)
.values(data)
.returning();
revalidatePath("/checklists");
return result;
}
export async function updateTemplate(
id: string,
data: Partial<{
name: string;
type: "daily" | "regular" | "special" | "equipment";
description: string;
isActive: boolean;
}>
) {
const [result] = await db
.update(checklistTemplates)
.set({ ...data, updatedAt: new Date() })
.where(eq(checklistTemplates.id, id))
.returning();
revalidatePath("/checklists");
return result;
}
export async function deleteTemplate(id: string) {
await db.delete(checklistTemplates).where(eq(checklistTemplates.id, id));
revalidatePath("/checklists");
}
export async function addTemplateItem(
templateId: string,
data: { content: string; category?: string; standard?: string }
) {
const existing = await db.query.checklistTemplateItems.findMany({
where: eq(checklistTemplateItems.templateId, templateId),
columns: { sortOrder: true },
});
const maxOrder = existing.reduce((max, i) => Math.max(max, i.sortOrder), -1);
const [result] = await db
.insert(checklistTemplateItems)
.values({ templateId, sortOrder: maxOrder + 1, ...data })
.returning();
revalidatePath("/checklists");
return result;
}
export async function updateTemplateItem(
itemId: string,
data: Partial<{ content: string; category: string; standard: string; sortOrder: number }>
) {
const [result] = await db
.update(checklistTemplateItems)
.set(data)
.where(eq(checklistTemplateItems.id, itemId))
.returning();
revalidatePath("/checklists");
return result;
}
export async function deleteTemplateItem(itemId: string) {
await db
.delete(checklistTemplateItems)
.where(eq(checklistTemplateItems.id, itemId));
revalidatePath("/checklists");
}
// ─── Inspections ───
export async function getInspections() {
return db.query.inspections.findMany({
orderBy: [desc(inspections.createdAt)],
with: {
template: true,
results: true,
nonConformances: true,
},
});
}
export async function getInspection(id: string) {
return db.query.inspections.findFirst({
where: eq(inspections.id, id),
with: {
template: { with: { items: { orderBy: [asc(checklistTemplateItems.sortOrder)] } } },
results: true,
nonConformances: true,
},
});
}
export async function createInspection(data: {
templateId: string;
inspector?: string;
location?: string;
}) {
const [result] = await db.insert(inspections).values(data).returning();
revalidatePath("/checklists/inspections");
return result;
}
export async function saveInspectionResults(
inspectionId: string,
results: Array<{
templateItemId: string;
result: string;
memo?: string;
}>
) {
// Delete existing results for this inspection
await db
.delete(inspectionResults)
.where(eq(inspectionResults.inspectionId, inspectionId));
if (results.length > 0) {
await db.insert(inspectionResults).values(
results.map((r) => ({
inspectionId,
templateItemId: r.templateItemId,
result: r.result,
memo: r.memo,
}))
);
}
revalidatePath(`/checklists/inspections/${inspectionId}`);
}
export async function completeInspection(inspectionId: string) {
await db
.update(inspections)
.set({ completedAt: new Date() })
.where(eq(inspections.id, inspectionId));
revalidatePath("/checklists/inspections");
}
// ─── Non-conformances ───
export async function getNonConformances() {
return db.query.nonConformances.findMany({
orderBy: [desc(nonConformances.createdAt)],
with: { inspection: { with: { template: true } } },
});
}
export async function createNonConformance(data: {
inspectionId: string;
inspectionResultId?: string;
description: string;
severity?: string;
responsiblePerson?: string;
dueDate?: string;
}) {
const [result] = await db
.insert(nonConformances)
.values({
...data,
dueDate: data.dueDate ? new Date(data.dueDate) : undefined,
})
.returning();
revalidatePath("/checklists");
return result;
}
export async function updateNonConformance(
id: string,
data: Partial<{
status: "open" | "corrective_action" | "verification" | "closed";
correctiveAction: string;
responsiblePerson: string;
closedBy: string;
}>
) {
const updateData: Record<string, unknown> = {
...data,
updatedAt: new Date(),
};
if (data.status === "closed") {
updateData.closedAt = new Date();
}
const [result] = await db
.update(nonConformances)
.set(updateData)
.where(eq(nonConformances.id, id))
.returning();
revalidatePath("/checklists");
return result;
}