pelagia-portal/App/components/layout/header.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

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