mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
feat: add the company profile support to the ui
This commit is contained in:
@@ -146,10 +146,11 @@ const sidebarItems: SidebarItem[] = [
|
||||
const App = () => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const { user } = useAuth();
|
||||
const { user, company } = useAuth();
|
||||
|
||||
const displayName = user?.name?.en || user?.username || user?.email || "User";
|
||||
const userEmail = user?.email;
|
||||
const companyProfiles = company?.company?.companyProfiles ?? [];
|
||||
|
||||
return (
|
||||
<Routes>
|
||||
@@ -190,6 +191,7 @@ const App = () => {
|
||||
enableThemeToggle
|
||||
userName={displayName}
|
||||
userEmail={userEmail}
|
||||
companyProfiles={companyProfiles}
|
||||
>
|
||||
<Outlet />
|
||||
</AppLayout>
|
||||
|
||||
@@ -47,9 +47,19 @@ export interface AppLayoutProps {
|
||||
enableThemeToggle?: boolean;
|
||||
userName?: string;
|
||||
userEmail?: string;
|
||||
/** Operational profiles for the company — surfaced as reference chips in the account menu. */
|
||||
companyProfiles?: { type: string; reference: string; status?: string }[];
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const PROFILE_TYPE_LABELS: Record<string, string> = {
|
||||
importer: "Importer",
|
||||
exporter: "Exporter",
|
||||
freight_forwarder: "Freight Forwarder",
|
||||
dj_freight_forwarder: "DJ Freight Forwarder",
|
||||
transporter: "Transporter",
|
||||
};
|
||||
|
||||
function getInitials(name: string): string {
|
||||
return name
|
||||
.split(" ")
|
||||
@@ -106,6 +116,7 @@ export function AppLayout({
|
||||
enableThemeToggle = false,
|
||||
userName = "User",
|
||||
userEmail,
|
||||
companyProfiles = [],
|
||||
children,
|
||||
}: AppLayoutProps) {
|
||||
const [mobileOpen, { toggle: toggleMobile }] = useDisclosure();
|
||||
@@ -306,6 +317,34 @@ export function AppLayout({
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
{companyProfiles.length > 0 && (
|
||||
<>
|
||||
<Divider />
|
||||
<Box px="sm" py="xs">
|
||||
<Stack gap={6}>
|
||||
{companyProfiles.map((p) => (
|
||||
<Group
|
||||
key={p.reference}
|
||||
justify="space-between"
|
||||
gap="sm"
|
||||
wrap="nowrap"
|
||||
>
|
||||
<Text
|
||||
size="xs"
|
||||
fw={600}
|
||||
style={{ color: textColor }}
|
||||
>
|
||||
{PROFILE_TYPE_LABELS[p.type] ?? p.type}
|
||||
</Text>
|
||||
<Text size="xs" ff="monospace" c="dimmed">
|
||||
{p.reference}
|
||||
</Text>
|
||||
</Group>
|
||||
))}
|
||||
</Stack>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
<Divider />
|
||||
<Menu.Item
|
||||
leftSection={<User size={15} />}
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
} from "@mantine/core";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import {
|
||||
BadgeCheck,
|
||||
Briefcase,
|
||||
Building,
|
||||
Building2,
|
||||
@@ -26,11 +27,13 @@ import {
|
||||
Mail,
|
||||
MapPin,
|
||||
Phone,
|
||||
Plus,
|
||||
ShieldCheck,
|
||||
User,
|
||||
UserCheck,
|
||||
} from "lucide-react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { rolesForCompanyType } from "./settings/companyRoles";
|
||||
|
||||
function InfoItem({
|
||||
icon,
|
||||
@@ -129,6 +132,12 @@ export default function ProfilePage() {
|
||||
);
|
||||
}
|
||||
|
||||
// Registered operational profiles keyed by type, plus the roles this company
|
||||
// type may hold (importer/exporter for a customer). Mirrors CompanyRolesCard.
|
||||
const refByType = new Map(profile.companyProfiles.map((p) => [p.type, p]));
|
||||
const roleOptions = rolesForCompanyType(profile.companyType);
|
||||
const activeOptions = roleOptions.filter((o) => refByType.has(o.type));
|
||||
|
||||
return (
|
||||
<Container size="xl" px="lg" py="xl">
|
||||
{/* Header */}
|
||||
@@ -145,12 +154,28 @@ export default function ProfilePage() {
|
||||
Verified
|
||||
</Badge>
|
||||
</Group>
|
||||
<Group gap={6} c="edr-muted">
|
||||
<Building size={16} />
|
||||
<Text c="edr-muted" fw={500}>
|
||||
{profile.companyType}
|
||||
</Text>
|
||||
</Group>
|
||||
{activeOptions.length > 0 ? (
|
||||
<Group gap="xs">
|
||||
{activeOptions.map((opt) => (
|
||||
<Badge
|
||||
key={opt.type}
|
||||
variant="light"
|
||||
color="edr-green"
|
||||
size="lg"
|
||||
radius="sm"
|
||||
>
|
||||
{opt.label} · {refByType.get(opt.type)!.reference}
|
||||
</Badge>
|
||||
))}
|
||||
</Group>
|
||||
) : (
|
||||
<Group gap={6} c="edr-muted">
|
||||
<Building size={16} />
|
||||
<Text c="edr-muted" fw={500}>
|
||||
{profile.companyType}
|
||||
</Text>
|
||||
</Group>
|
||||
)}
|
||||
</Stack>
|
||||
</Group>
|
||||
|
||||
@@ -163,24 +188,58 @@ export default function ProfilePage() {
|
||||
{/* Company Details */}
|
||||
<Card>
|
||||
<CardHeading
|
||||
icon={<Building2 size={20} color="var(--mantine-color-edr-green-6)" />}
|
||||
icon={
|
||||
<Building2
|
||||
size={20}
|
||||
color="var(--mantine-color-edr-green-6)"
|
||||
/>
|
||||
}
|
||||
title="Company Details"
|
||||
description="Business registration information"
|
||||
/>
|
||||
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="md">
|
||||
<InfoItem icon={<Globe size={16} />} label="Location" value={profile.companyLocation} />
|
||||
<InfoItem icon={<MapPin size={16} />} label="Address" value={profile.companyAddress} />
|
||||
<InfoItem icon={<FileCheck size={16} />} label="TIN Number" value={profile.tinNumber} />
|
||||
<InfoItem icon={<ShieldCheck size={16} />} label="FAN Number" value={profile.fanNumber} />
|
||||
<InfoItem icon={<Mail size={16} />} label="Email" value={profile.companyEmail} />
|
||||
<InfoItem icon={<Phone size={16} />} label="Phone" value={profile.companyPhone} />
|
||||
<InfoItem
|
||||
icon={<Globe size={16} />}
|
||||
label="Location"
|
||||
value={profile.companyLocation}
|
||||
/>
|
||||
<InfoItem
|
||||
icon={<MapPin size={16} />}
|
||||
label="Address"
|
||||
value={profile.companyAddress}
|
||||
/>
|
||||
<InfoItem
|
||||
icon={<FileCheck size={16} />}
|
||||
label="TIN Number"
|
||||
value={profile.tinNumber}
|
||||
/>
|
||||
<InfoItem
|
||||
icon={<ShieldCheck size={16} />}
|
||||
label="FAN Number"
|
||||
value={profile.fanNumber}
|
||||
/>
|
||||
<InfoItem
|
||||
icon={<Mail size={16} />}
|
||||
label="Email"
|
||||
value={profile.companyEmail}
|
||||
/>
|
||||
<InfoItem
|
||||
icon={<Phone size={16} />}
|
||||
label="Phone"
|
||||
value={profile.companyPhone}
|
||||
/>
|
||||
</SimpleGrid>
|
||||
</Card>
|
||||
|
||||
{/* Key Personnel */}
|
||||
<Card>
|
||||
<CardHeading
|
||||
icon={<Briefcase size={20} color="var(--mantine-color-edr-green-6)" />}
|
||||
icon={
|
||||
<Briefcase
|
||||
size={20}
|
||||
color="var(--mantine-color-edr-green-6)"
|
||||
/>
|
||||
}
|
||||
title="Key Personnel"
|
||||
description="Management and contact persons"
|
||||
/>
|
||||
@@ -201,7 +260,12 @@ export default function ProfilePage() {
|
||||
{profile.poaName && (
|
||||
<Card style={{ borderStyle: "dashed" }}>
|
||||
<CardHeading
|
||||
icon={<UserCheck size={20} color="var(--mantine-color-edr-accent-6)" />}
|
||||
icon={
|
||||
<UserCheck
|
||||
size={20}
|
||||
color="var(--mantine-color-edr-accent-6)"
|
||||
/>
|
||||
}
|
||||
title="Power of Attorney"
|
||||
description="Authorized representative details"
|
||||
/>
|
||||
@@ -218,37 +282,127 @@ export default function ProfilePage() {
|
||||
|
||||
{/* Right Column */}
|
||||
<Grid.Col span={{ base: 12, lg: 4 }}>
|
||||
<Card
|
||||
padding="xl"
|
||||
style={{
|
||||
background: "var(--mantine-color-edr-ink-6)",
|
||||
position: "relative",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<Box style={{ position: "absolute", top: 16, right: 16, opacity: 0.1 }}>
|
||||
<ShieldCheck size={128} color="white" />
|
||||
</Box>
|
||||
<Stack gap="md" style={{ position: "relative", zIndex: 1 }}>
|
||||
<Title order={3} size="h4" c="white">
|
||||
Secure Account
|
||||
</Title>
|
||||
<Text size="sm" c="gray.4">
|
||||
Your information is protected by enterprise-grade security. Contact
|
||||
support for verified information updates.
|
||||
</Text>
|
||||
<Button
|
||||
component={Link}
|
||||
to="/settings"
|
||||
variant="white"
|
||||
color="dark"
|
||||
mt="xs"
|
||||
w="fit-content"
|
||||
<Stack gap="lg">
|
||||
{/* Operating Roles */}
|
||||
<Card>
|
||||
<CardHeading
|
||||
icon={
|
||||
<BadgeCheck
|
||||
size={20}
|
||||
color="var(--mantine-color-edr-green-6)"
|
||||
/>
|
||||
}
|
||||
title="Operating Roles"
|
||||
description="Your registered freight roles and reference numbers"
|
||||
/>
|
||||
{roleOptions.length === 0 ? (
|
||||
<Text size="sm" c="edr-muted">
|
||||
Role management for this company type is coming soon.
|
||||
</Text>
|
||||
) : (
|
||||
<Stack gap="md">
|
||||
{roleOptions.map((opt) => {
|
||||
const active = refByType.get(opt.type);
|
||||
return (
|
||||
<Group
|
||||
key={opt.type}
|
||||
justify="space-between"
|
||||
wrap="nowrap"
|
||||
align="center"
|
||||
>
|
||||
<Group gap="sm" wrap="nowrap">
|
||||
<ThemeIcon
|
||||
variant="light"
|
||||
color="edr-green"
|
||||
size="lg"
|
||||
radius="md"
|
||||
>
|
||||
{opt.icon}
|
||||
</ThemeIcon>
|
||||
<Stack gap={2}>
|
||||
<Text size="sm" fw={600} c="edr-text">
|
||||
{opt.label}
|
||||
</Text>
|
||||
<Text
|
||||
size="xs"
|
||||
c="edr-muted"
|
||||
ff={active ? "monospace" : undefined}
|
||||
>
|
||||
{active ? active.reference : "Not registered"}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Group>
|
||||
{active ? (
|
||||
<Badge
|
||||
variant="light"
|
||||
color={
|
||||
active.status === "active"
|
||||
? "edr-green"
|
||||
: "edr-accent"
|
||||
}
|
||||
tt="capitalize"
|
||||
>
|
||||
{active.status}
|
||||
</Badge>
|
||||
) : (
|
||||
<Button
|
||||
component={Link}
|
||||
to="/settings?tab=company"
|
||||
size="xs"
|
||||
variant="light"
|
||||
color="edr-green"
|
||||
leftSection={<Plus size={14} />}
|
||||
>
|
||||
Add {opt.label}
|
||||
</Button>
|
||||
)}
|
||||
</Group>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
{/* Secure Account */}
|
||||
<Card
|
||||
padding="xl"
|
||||
style={{
|
||||
background: "var(--mantine-color-edr-ink-6)",
|
||||
position: "relative",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 16,
|
||||
right: 16,
|
||||
opacity: 0.1,
|
||||
}}
|
||||
>
|
||||
Edit Settings
|
||||
</Button>
|
||||
</Stack>
|
||||
</Card>
|
||||
<ShieldCheck size={128} color="white" />
|
||||
</Box>
|
||||
<Stack gap="md" style={{ position: "relative", zIndex: 1 }}>
|
||||
<Title order={3} size="h4" c="white">
|
||||
Secure Account
|
||||
</Title>
|
||||
<Text size="sm" c="gray.4">
|
||||
Your information is protected by enterprise-grade security.
|
||||
Contact support for verified information updates.
|
||||
</Text>
|
||||
<Button
|
||||
component={Link}
|
||||
to="/settings"
|
||||
variant="white"
|
||||
color="dark"
|
||||
mt="xs"
|
||||
w="fit-content"
|
||||
>
|
||||
Edit Settings
|
||||
</Button>
|
||||
</Stack>
|
||||
</Card>
|
||||
</Stack>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</Container>
|
||||
|
||||
Reference in New Issue
Block a user