mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 16:35:42 +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 App = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { user } = useAuth();
|
const { user, company } = useAuth();
|
||||||
|
|
||||||
const displayName = user?.name?.en || user?.username || user?.email || "User";
|
const displayName = user?.name?.en || user?.username || user?.email || "User";
|
||||||
const userEmail = user?.email;
|
const userEmail = user?.email;
|
||||||
|
const companyProfiles = company?.company?.companyProfiles ?? [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Routes>
|
<Routes>
|
||||||
@@ -190,6 +191,7 @@ const App = () => {
|
|||||||
enableThemeToggle
|
enableThemeToggle
|
||||||
userName={displayName}
|
userName={displayName}
|
||||||
userEmail={userEmail}
|
userEmail={userEmail}
|
||||||
|
companyProfiles={companyProfiles}
|
||||||
>
|
>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</AppLayout>
|
</AppLayout>
|
||||||
|
|||||||
@@ -47,9 +47,19 @@ export interface AppLayoutProps {
|
|||||||
enableThemeToggle?: boolean;
|
enableThemeToggle?: boolean;
|
||||||
userName?: string;
|
userName?: string;
|
||||||
userEmail?: 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;
|
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 {
|
function getInitials(name: string): string {
|
||||||
return name
|
return name
|
||||||
.split(" ")
|
.split(" ")
|
||||||
@@ -106,6 +116,7 @@ export function AppLayout({
|
|||||||
enableThemeToggle = false,
|
enableThemeToggle = false,
|
||||||
userName = "User",
|
userName = "User",
|
||||||
userEmail,
|
userEmail,
|
||||||
|
companyProfiles = [],
|
||||||
children,
|
children,
|
||||||
}: AppLayoutProps) {
|
}: AppLayoutProps) {
|
||||||
const [mobileOpen, { toggle: toggleMobile }] = useDisclosure();
|
const [mobileOpen, { toggle: toggleMobile }] = useDisclosure();
|
||||||
@@ -306,6 +317,34 @@ export function AppLayout({
|
|||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</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 />
|
<Divider />
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
leftSection={<User size={15} />}
|
leftSection={<User size={15} />}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import {
|
import {
|
||||||
|
BadgeCheck,
|
||||||
Briefcase,
|
Briefcase,
|
||||||
Building,
|
Building,
|
||||||
Building2,
|
Building2,
|
||||||
@@ -26,11 +27,13 @@ import {
|
|||||||
Mail,
|
Mail,
|
||||||
MapPin,
|
MapPin,
|
||||||
Phone,
|
Phone,
|
||||||
|
Plus,
|
||||||
ShieldCheck,
|
ShieldCheck,
|
||||||
User,
|
User,
|
||||||
UserCheck,
|
UserCheck,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import { rolesForCompanyType } from "./settings/companyRoles";
|
||||||
|
|
||||||
function InfoItem({
|
function InfoItem({
|
||||||
icon,
|
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 (
|
return (
|
||||||
<Container size="xl" px="lg" py="xl">
|
<Container size="xl" px="lg" py="xl">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
@@ -145,12 +154,28 @@ export default function ProfilePage() {
|
|||||||
Verified
|
Verified
|
||||||
</Badge>
|
</Badge>
|
||||||
</Group>
|
</Group>
|
||||||
<Group gap={6} c="edr-muted">
|
{activeOptions.length > 0 ? (
|
||||||
<Building size={16} />
|
<Group gap="xs">
|
||||||
<Text c="edr-muted" fw={500}>
|
{activeOptions.map((opt) => (
|
||||||
{profile.companyType}
|
<Badge
|
||||||
</Text>
|
key={opt.type}
|
||||||
</Group>
|
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>
|
</Stack>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
@@ -163,24 +188,58 @@ export default function ProfilePage() {
|
|||||||
{/* Company Details */}
|
{/* Company Details */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeading
|
<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"
|
title="Company Details"
|
||||||
description="Business registration information"
|
description="Business registration information"
|
||||||
/>
|
/>
|
||||||
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="md">
|
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="md">
|
||||||
<InfoItem icon={<Globe size={16} />} label="Location" value={profile.companyLocation} />
|
<InfoItem
|
||||||
<InfoItem icon={<MapPin size={16} />} label="Address" value={profile.companyAddress} />
|
icon={<Globe size={16} />}
|
||||||
<InfoItem icon={<FileCheck size={16} />} label="TIN Number" value={profile.tinNumber} />
|
label="Location"
|
||||||
<InfoItem icon={<ShieldCheck size={16} />} label="FAN Number" value={profile.fanNumber} />
|
value={profile.companyLocation}
|
||||||
<InfoItem icon={<Mail size={16} />} label="Email" value={profile.companyEmail} />
|
/>
|
||||||
<InfoItem icon={<Phone size={16} />} label="Phone" value={profile.companyPhone} />
|
<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>
|
</SimpleGrid>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Key Personnel */}
|
{/* Key Personnel */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeading
|
<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"
|
title="Key Personnel"
|
||||||
description="Management and contact persons"
|
description="Management and contact persons"
|
||||||
/>
|
/>
|
||||||
@@ -201,7 +260,12 @@ export default function ProfilePage() {
|
|||||||
{profile.poaName && (
|
{profile.poaName && (
|
||||||
<Card style={{ borderStyle: "dashed" }}>
|
<Card style={{ borderStyle: "dashed" }}>
|
||||||
<CardHeading
|
<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"
|
title="Power of Attorney"
|
||||||
description="Authorized representative details"
|
description="Authorized representative details"
|
||||||
/>
|
/>
|
||||||
@@ -218,37 +282,127 @@ export default function ProfilePage() {
|
|||||||
|
|
||||||
{/* Right Column */}
|
{/* Right Column */}
|
||||||
<Grid.Col span={{ base: 12, lg: 4 }}>
|
<Grid.Col span={{ base: 12, lg: 4 }}>
|
||||||
<Card
|
<Stack gap="lg">
|
||||||
padding="xl"
|
{/* Operating Roles */}
|
||||||
style={{
|
<Card>
|
||||||
background: "var(--mantine-color-edr-ink-6)",
|
<CardHeading
|
||||||
position: "relative",
|
icon={
|
||||||
overflow: "hidden",
|
<BadgeCheck
|
||||||
}}
|
size={20}
|
||||||
>
|
color="var(--mantine-color-edr-green-6)"
|
||||||
<Box style={{ position: "absolute", top: 16, right: 16, opacity: 0.1 }}>
|
/>
|
||||||
<ShieldCheck size={128} color="white" />
|
}
|
||||||
</Box>
|
title="Operating Roles"
|
||||||
<Stack gap="md" style={{ position: "relative", zIndex: 1 }}>
|
description="Your registered freight roles and reference numbers"
|
||||||
<Title order={3} size="h4" c="white">
|
/>
|
||||||
Secure Account
|
{roleOptions.length === 0 ? (
|
||||||
</Title>
|
<Text size="sm" c="edr-muted">
|
||||||
<Text size="sm" c="gray.4">
|
Role management for this company type is coming soon.
|
||||||
Your information is protected by enterprise-grade security. Contact
|
</Text>
|
||||||
support for verified information updates.
|
) : (
|
||||||
</Text>
|
<Stack gap="md">
|
||||||
<Button
|
{roleOptions.map((opt) => {
|
||||||
component={Link}
|
const active = refByType.get(opt.type);
|
||||||
to="/settings"
|
return (
|
||||||
variant="white"
|
<Group
|
||||||
color="dark"
|
key={opt.type}
|
||||||
mt="xs"
|
justify="space-between"
|
||||||
w="fit-content"
|
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
|
<ShieldCheck size={128} color="white" />
|
||||||
</Button>
|
</Box>
|
||||||
</Stack>
|
<Stack gap="md" style={{ position: "relative", zIndex: 1 }}>
|
||||||
</Card>
|
<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.Col>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
Reference in New Issue
Block a user