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>
This commit is contained in:
224
src/actions/checklists.ts
Normal file
224
src/actions/checklists.ts
Normal file
@@ -0,0 +1,224 @@
|
||||
"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;
|
||||
}
|
||||
Reference in New Issue
Block a user