Phase 1 of the Crewing module per wiki Crewing-Implementation-Spec §12, all dark behind NEXT_PUBLIC_CREWING_ENABLED (off by default — production unchanged). - schema: add SITE_STAFF to Role; add Rank (self-referential org hierarchy, like Account) + RankDocRequirement, RankCategory & SeafarerDocType enums. - permissions: full §6 crewing grant matrix (PO_ROLE_PERMISSIONS + CREWING_ROLE_PERMISSIONS merged); SITE_STAFF row; MPO has no attendance/leave, approvals are Manager-only, manage_ranks is Manager+Admin. - feature flag: CREWING_ENABLED (opt-in "true"). - nav: flag-gated Crewing section scaffold + "Ranks & documents" under Admin. - reference data: rank-data.ts + rank-doc-data.ts seeded via shared seed-ranks.ts in both dev and prod seeds (19 ranks, 118 doc requirements). - screen: /admin/ranks — rank hierarchy card + per-rank required-documents card. - role-label/prefix maps updated for the new role. Tests: unit (permission matrix + flag), integration (ranks admin CRUD, parent linking, cycle/children guards, doc-requirement upsert/remove, permission gating). Docs: CLAUDE.md "Crewing (feature-flagged)" section + env var. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
44 lines
1.3 KiB
TypeScript
44 lines
1.3 KiB
TypeScript
import { auth } from "@/auth";
|
|
import { db } from "@/lib/db";
|
|
import { hasPermission } from "@/lib/permissions";
|
|
import { CREWING_ENABLED } from "@/lib/feature-flags";
|
|
import { redirect, notFound } from "next/navigation";
|
|
import { RanksManager } from "./ranks-manager";
|
|
import type { Metadata } from "next";
|
|
|
|
export const metadata: Metadata = { title: "Ranks & Documents" };
|
|
|
|
export default async function AdminRanksPage() {
|
|
// Dark unless the crewing module is switched on.
|
|
if (!CREWING_ENABLED) notFound();
|
|
|
|
const session = await auth();
|
|
if (!session?.user) redirect("/login");
|
|
if (!hasPermission(session.user.role, "manage_ranks")) redirect("/dashboard");
|
|
|
|
const ranks = await db.rank.findMany({
|
|
orderBy: [{ name: "asc" }],
|
|
include: { docRequirements: { orderBy: { docType: "asc" } } },
|
|
});
|
|
|
|
// Flatten to plain props (no Date/Decimal crosses the server→client boundary).
|
|
const rows = ranks.map((r) => ({
|
|
id: r.id,
|
|
code: r.code,
|
|
name: r.name,
|
|
description: r.description,
|
|
category: r.category,
|
|
isSeafarer: r.isSeafarer,
|
|
grantsLogin: r.grantsLogin,
|
|
isActive: r.isActive,
|
|
parentId: r.parentId,
|
|
docRequirements: r.docRequirements.map((d) => ({
|
|
id: d.id,
|
|
docType: d.docType,
|
|
isMandatory: d.isMandatory,
|
|
note: d.note,
|
|
})),
|
|
}));
|
|
|
|
return <RanksManager ranks={rows} />;
|
|
}
|