Office/admin crewing-management surface behind a new manage_crew permission (Manager + SuperUser + Admin). Stacks on 4b. Behind NEXT_PUBLIC_CREWING_ENABLED. What's in - Permission: manage_crew added to the §6 matrix (MGR/SU/ADMIN). - Direct placement (placeCrew): a Manager assigns a crew member to a vessel/site WITHOUT a requisition — creates an ACTIVE CrewAssignment, promotes a candidate to EMPLOYEE with a CRW- number (generateEmployeeId), blocked if already actively assigned. - Admin crew CRUD: createCrewMember / updateCrewMember / deleteCrewMember (delete blocked when assignments/applications exist). - Crew strength config: upsert/delete VesselRankRequirement (the minStrength that drives R6 leave-clash detection). - Screens under Administration (flag-gated, MGR/SU/ADMIN): /admin/crew (list + add/ edit/delete + Place modal) and /admin/crew-strength (requirement table + form). Tests & docs - Unit: permissions-crewing.test.ts gains a manage_crew check. Integration: crewing-admin.test.ts (9) — CRUD, delete guard, direct placement (+promotion, +active-assignment guard), strength upsert/delete, manage_crew gating. type-check clean; full unit (241) + integration (192) green. - CLAUDE.md updated with the crewing-admin surface. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
34 lines
1.3 KiB
TypeScript
34 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 { CrewStrengthManager } from "./crew-strength-manager";
|
|
import type { Metadata } from "next";
|
|
|
|
export const metadata: Metadata = { title: "Crew strength" };
|
|
|
|
export default async function CrewStrengthPage() {
|
|
if (!CREWING_ENABLED) notFound();
|
|
|
|
const session = await auth();
|
|
if (!session?.user) redirect("/login");
|
|
if (!hasPermission(session.user.role, "manage_crew")) redirect("/dashboard");
|
|
|
|
const [requirements, vessels, ranks] = await Promise.all([
|
|
db.vesselRankRequirement.findMany({
|
|
orderBy: [{ vessel: { name: "asc" } }, { rank: { name: "asc" } }],
|
|
include: { vessel: { select: { name: true } }, rank: { select: { name: true } } },
|
|
}),
|
|
db.vessel.findMany({ where: { isActive: true }, orderBy: { name: "asc" }, select: { id: true, name: true } }),
|
|
db.rank.findMany({ where: { isActive: true }, orderBy: { name: "asc" }, select: { id: true, code: true, name: true } }),
|
|
]);
|
|
|
|
return (
|
|
<CrewStrengthManager
|
|
requirements={requirements.map((r) => ({ id: r.id, vessel: r.vessel.name, rank: r.rank.name, minStrength: r.minStrength }))}
|
|
vessels={vessels}
|
|
ranks={ranks}
|
|
/>
|
|
);
|
|
}
|