mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-07 16:19:01 +00:00
No Vendor opens the work order form on vendor assignment and Vendor Conflict links each work order, as the vendor reminders did. Header baselines include the notification bell, and the top bar user menu moves to its own component to stay within the function-size gate. The vendor e2e now covers the feed contract.
127 lines
3.9 KiB
TypeScript
127 lines
3.9 KiB
TypeScript
import type { LucideIcon } from "lucide-react";
|
|
import {
|
|
AlertTriangle,
|
|
Bell,
|
|
Clock,
|
|
Inbox,
|
|
MessageSquare,
|
|
ShieldAlert,
|
|
Wrench,
|
|
X,
|
|
} from "lucide-react";
|
|
import { Link } from "react-router";
|
|
import { Text } from "@/components/ui/text";
|
|
import type { NotificationItem } from "@/domain/notifications/types/notification-feed";
|
|
import { workOrderViewUrl } from "@/domain/notifications/utils/notification-target-url";
|
|
import { isDismissable } from "@/domain/notifications/utils/visible-notification-sections";
|
|
|
|
const REASON_ICONS: Record<string, LucideIcon> = {
|
|
"severity-critical": AlertTriangle,
|
|
sla: AlertTriangle,
|
|
mentions: MessageSquare,
|
|
unassigned: Inbox,
|
|
"due-soon": Clock,
|
|
"no-vendor": Wrench,
|
|
"aveta-missing": ShieldAlert,
|
|
activity: MessageSquare,
|
|
"vendor-conflict": Wrench,
|
|
};
|
|
|
|
type NotificationRowProps = {
|
|
item: NotificationItem;
|
|
unread: boolean;
|
|
onOpen: (item: NotificationItem, url?: string) => void;
|
|
onDismiss: (item: NotificationItem) => void;
|
|
};
|
|
|
|
/** A vendor conflict spans several work orders, so each one stays reachable from the row. */
|
|
function WorkOrderLinks({ item, onOpen }: Pick<NotificationRowProps, "item" | "onOpen">) {
|
|
if (item.reason !== "vendor-conflict" || item.workOrders.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<span className="flex flex-wrap gap-2" style={{ paddingLeft: 31, marginTop: 2 }}>
|
|
{item.workOrders.map((workOrder) => {
|
|
const url = workOrderViewUrl(workOrder.id);
|
|
return (
|
|
<Link
|
|
key={workOrder.id}
|
|
to={url}
|
|
onClick={(event) => {
|
|
event.preventDefault();
|
|
onOpen(item, url);
|
|
}}
|
|
className="text-xs underline-offset-2 hover:underline"
|
|
style={{ color: "var(--color-primary)" }}
|
|
>
|
|
{workOrder.number != null ? `WO #${workOrder.number}` : `WO ${workOrder.id}`}
|
|
</Link>
|
|
);
|
|
})}
|
|
</span>
|
|
);
|
|
}
|
|
|
|
export function NotificationRow({ item, unread, onOpen, onDismiss }: NotificationRowProps) {
|
|
const Icon = REASON_ICONS[item.reason] ?? Bell;
|
|
|
|
return (
|
|
<li
|
|
className="group flex items-start gap-2.5 transition-colors hover:bg-[var(--color-bg-muted)]"
|
|
style={{
|
|
padding: "10px 8px",
|
|
marginInline: -8,
|
|
borderRadius: "var(--radius-md)",
|
|
borderBottom: "1px solid var(--color-bg-muted)",
|
|
}}
|
|
data-unread={unread}
|
|
>
|
|
<span className="flex min-w-0 flex-1 flex-col">
|
|
<button
|
|
type="button"
|
|
onClick={() => onOpen(item)}
|
|
className="flex min-w-0 flex-1 cursor-pointer items-start gap-2.5 text-left"
|
|
>
|
|
<span
|
|
aria-hidden
|
|
className="shrink-0 rounded-full"
|
|
style={{
|
|
width: 6,
|
|
height: 6,
|
|
marginTop: 7,
|
|
background: unread ? "var(--color-primary)" : "transparent",
|
|
}}
|
|
/>
|
|
<Icon
|
|
size={15}
|
|
aria-hidden
|
|
className="shrink-0"
|
|
style={{ color: "var(--color-text-muted)", marginTop: 2 }}
|
|
/>
|
|
<Text
|
|
as="span"
|
|
variant="description"
|
|
sx={{ fontSize: 13, lineHeight: 1.45, fontWeight: unread ? 500 : 400, margin: 0 }}
|
|
>
|
|
{unread && <span className="sr-only">Unread: </span>}
|
|
{item.title}
|
|
</Text>
|
|
</button>
|
|
<WorkOrderLinks item={item} onOpen={onOpen} />
|
|
</span>
|
|
{isDismissable(item) && (
|
|
<button
|
|
type="button"
|
|
onClick={() => onDismiss(item)}
|
|
title="Dismiss"
|
|
aria-label="Dismiss notification"
|
|
className="shrink-0 cursor-pointer opacity-0 transition-opacity group-hover:opacity-100 focus-visible:opacity-100"
|
|
style={{ color: "var(--color-text-muted)", marginTop: 2 }}
|
|
>
|
|
<X size={13} aria-hidden />
|
|
</button>
|
|
)}
|
|
</li>
|
|
);
|
|
}
|