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>
63 lines
1.9 KiB
TypeScript
63 lines
1.9 KiB
TypeScript
"use client";
|
|
|
|
import { signOut } from "next-auth/react";
|
|
import { LogOut } from "lucide-react";
|
|
import type { Role } from "@prisma/client";
|
|
import { CartIcon } from "./cart-icon";
|
|
import { NotificationBell } from "./notification-bell";
|
|
import { ReportIssueButton } from "./report-issue-button";
|
|
|
|
const ROLE_LABELS: Record<Role, string> = {
|
|
TECHNICAL: "Technical",
|
|
MANNING: "Manning",
|
|
ACCOUNTS: "Accounts",
|
|
MANAGER: "Manager",
|
|
SUPERUSER: "SuperUser",
|
|
AUDITOR: "Auditor",
|
|
ADMIN: "Admin",
|
|
SITE_STAFF: "Site Staff",
|
|
};
|
|
|
|
const CART_ROLES: Role[] = ["TECHNICAL", "MANNING", "SUPERUSER", "MANAGER"];
|
|
|
|
interface NotificationItem {
|
|
id: string;
|
|
body: string;
|
|
link: string | null;
|
|
isRead: boolean;
|
|
sentAt: string;
|
|
poId: string | null;
|
|
}
|
|
|
|
interface HeaderProps {
|
|
user: { name: string; email: string; role: Role };
|
|
initialUnreadCount: number;
|
|
initialNotifications: NotificationItem[];
|
|
}
|
|
|
|
export function Header({ user, initialUnreadCount, initialNotifications }: HeaderProps) {
|
|
return (
|
|
<header className="flex h-16 items-center justify-between border-b border-neutral-200 bg-white px-6">
|
|
<div />
|
|
<div className="flex items-center gap-2">
|
|
{CART_ROLES.includes(user.role) && <CartIcon />}
|
|
<ReportIssueButton />
|
|
<NotificationBell
|
|
initialUnreadCount={initialUnreadCount}
|
|
initialNotifications={initialNotifications}
|
|
/>
|
|
<div className="text-right ml-2">
|
|
<p className="text-sm font-medium text-neutral-900">{user.name}</p>
|
|
<p className="text-xs text-neutral-500">{ROLE_LABELS[user.role]}</p>
|
|
</div>
|
|
<button
|
|
onClick={() => signOut({ callbackUrl: "/login" })}
|
|
className="flex items-center gap-1.5 rounded-lg p-2 text-neutral-500 hover:bg-neutral-100 hover:text-neutral-700 transition-colors"
|
|
title="Sign out"
|
|
>
|
|
<LogOut className="h-4 w-4" />
|
|
</button>
|
|
</div>
|
|
</header>
|
|
);
|
|
}
|