pelagia-portal/App/app/(portal)/admin/ranks/page.tsx
Hardik d0006a8fc7
All checks were successful
PR checks / checks (pull_request) Successful in 36s
PR checks / integration (pull_request) Successful in 28s
feat(crewing): foundations — SITE_STAFF role, ranks reference data + admin (flagged)
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>
2026-06-22 13:26:04 +05:30

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} />;
}