merge fixes

This commit is contained in:
Estifo77
2026-07-29 10:54:43 +03:00
parent e497b30bd1
commit 6ca48cbb9a
6 changed files with 170 additions and 642 deletions

View File

@@ -1,11 +1,11 @@
import { useCallback, useState } from 'react';
import { AppShell, rem } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import { Outlet, useLocation, useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { logout } from '@ema-platform/auth';
import { AppHeader, AppSidebar } from '@ema-platform/ui';
import type { NavItem } from '@ema-platform/ui';
import { useCallback, useState } from "react";
import { AppShell, rem } from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import { Outlet, useLocation, useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { logout } from "@ema-platform/auth";
import { AppHeader, AppSidebar } from "@ema-platform/ui";
import type { NavItem } from "@ema-platform/ui";
import {
IconAnchor,
IconBook2,
@@ -24,49 +24,104 @@ import {
IconQuestionMark,
IconClipboardList,
IconReport,
IconAnchor,
IconFilePlus,
IconGauge,
IconShieldOff,
IconShip,
IconStack2,
IconTruck,
} from '@tabler/icons-react';
import { notify } from '@ema-platform/ui';
import { SUPPORTED_LANGUAGES } from '../i18n/config';
import { useAppDispatch, useAppSelector } from '../store/hooks';
IconReportAnalytics,
} from "@tabler/icons-react";
import { notify } from "@ema-platform/ui";
import { SUPPORTED_LANGUAGES } from "../i18n/config";
import { useAppDispatch, useAppSelector } from "../store/hooks";
const NAV_ITEMS: NavItem[] = [
{ to: '/dashboard', label: 'nav.dashboard', icon: IconLayoutDashboard },
{ to: '/um/user-management/dashboard', label: 'nav.userManagement', icon: IconUserShield },
{ to: '/seaman-book-queue', label: 'nav.seamanBookQueue', icon: IconBook2 },
{ to: '/vessel-registration-head-dashboard', label: 'nav.vesselRegistrationHeadDashboard', icon: IconGauge },
{ to: '/vessel-registration-queue', label: 'nav.vesselRegistrationQueue', icon: IconAnchor },
{ to: '/vessel-registration-queue/new', label: 'nav.vesselFormBuilder', icon: IconFilePlus },
{ to: '/vessel-registration-report', label: 'nav.vesselRegistrationReport', icon: IconChartBar },
{ to: '/vessel-ownership-transfer', label: 'nav.ownershipTransferQueue', icon: IconFileDescription },
{ to: '/logistics-head-dashboard', label: 'nav.logisticsHeadDashboard', icon: IconGauge },
{ to: '/freight-forwarder-license', label: 'nav.freightForwarderLicense', icon: IconTruck },
{ to: '/shipping-agent-license', label: 'nav.shippingAgentLicense', icon: IconShip },
{ to: '/combined-license', label: 'nav.combinedLicense', icon: IconStack2 },
{ to: '/joint-investment-license', label: 'nav.jointInvestmentLicense', icon: IconUsers },
{ to: '/mto-license', label: 'nav.mtoLicense', icon: IconTruck },
{ to: '/waiver', label: 'nav.waiver', icon: IconShieldOff },
{ to: '/coc-queue', label: 'nav.cocQueue', icon: IconShieldCheck },
{ to: '/endorsement-queue', label: 'nav.endorsementQueue', icon: IconRubberStamp },
{ to: '/vessel-registrations', label: 'nav.vesselRegistrations', icon: IconAnchor },
{ to: '/vessel-registration-report', label: 'nav.vesselRegistrationReport', icon: IconReportAnalytics },
{ to: '/vessel-transfers', label: 'nav.vesselTransfers', icon: IconShip },
{ to: '/seafarer-registry', label: 'nav.seafarerRegistry', icon: IconUsers },
{ to: "/dashboard", label: "nav.dashboard", icon: IconLayoutDashboard },
{
to: "/um/user-management/dashboard",
label: "nav.userManagement",
icon: IconUserShield,
},
{ to: "/seaman-book-queue", label: "nav.seamanBookQueue", icon: IconBook2 },
{
to: "/vessel-registration-head-dashboard",
label: "nav.vesselRegistrationHeadDashboard",
icon: IconGauge,
},
{
to: "/vessel-registration-queue",
label: "nav.vesselRegistrationQueue",
icon: IconAnchor,
},
{
to: "/vessel-registration-queue/new",
label: "nav.vesselFormBuilder",
icon: IconFilePlus,
},
{
to: "/vessel-registration-report",
label: "nav.vesselRegistrationReport",
icon: IconChartBar,
},
{
to: "/vessel-ownership-transfer",
label: "nav.ownershipTransferQueue",
icon: IconFileDescription,
},
{
to: "/logistics-head-dashboard",
label: "nav.logisticsHeadDashboard",
icon: IconGauge,
},
{
to: "/freight-forwarder-license",
label: "nav.freightForwarderLicense",
icon: IconTruck,
},
{
to: "/shipping-agent-license",
label: "nav.shippingAgentLicense",
icon: IconShip,
},
{ to: "/combined-license", label: "nav.combinedLicense", icon: IconStack2 },
{
to: "/joint-investment-license",
label: "nav.jointInvestmentLicense",
icon: IconUsers,
},
{ to: "/mto-license", label: "nav.mtoLicense", icon: IconTruck },
{ to: "/waiver", label: "nav.waiver", icon: IconShieldOff },
{ to: "/coc-queue", label: "nav.cocQueue", icon: IconShieldCheck },
{
to: "/endorsement-queue",
label: "nav.endorsementQueue",
icon: IconRubberStamp,
},
{
to: "/vessel-registrations",
label: "nav.vesselRegistrations",
icon: IconAnchor,
},
{
to: "/vessel-registration-report",
label: "nav.vesselRegistrationReport",
icon: IconReportAnalytics,
},
{ to: "/vessel-transfers", label: "nav.vesselTransfers", icon: IconShip },
{ to: "/seafarer-registry", label: "nav.seafarerRegistry", icon: IconUsers },
// { to: '/applications', label: 'nav.applications', icon: IconFileDescription },
{ to: '/payment-config', label: 'nav.paymentConfig', icon: IconCreditCard },
{ to: '/analytics', label: 'nav.analytics', icon: IconChartBar },
{ to: '/medical-verification', label: 'nav.medicalVerification', icon: IconHeart },
{ to: '/questions', label: 'nav.questions', icon: IconQuestionMark },
{ to: '/exams', label: 'nav.exams', icon: IconClipboardList },
{ to: '/exam-results', label: 'nav.examResults', icon: IconReport },
{ to: '/configuration', label: 'nav.configuration', icon: IconSettings },
{ to: '/profile', label: 'nav.profile', icon: IconUser },
{ to: "/payment-config", label: "nav.paymentConfig", icon: IconCreditCard },
{ to: "/analytics", label: "nav.analytics", icon: IconChartBar },
{
to: "/medical-verification",
label: "nav.medicalVerification",
icon: IconHeart,
},
{ to: "/questions", label: "nav.questions", icon: IconQuestionMark },
{ to: "/exams", label: "nav.exams", icon: IconClipboardList },
{ to: "/exam-results", label: "nav.examResults", icon: IconReport },
{ to: "/configuration", label: "nav.configuration", icon: IconSettings },
{ to: "/profile", label: "nav.profile", icon: IconUser },
];
const HEADER_HEIGHT = 116;
@@ -81,24 +136,29 @@ export function BackofficeLayout() {
const user = useAppSelector((state) => state.auth.user);
const layoutMode = useAppSelector((state) => state.preferences.layoutMode);
const displayName = user?.name?.en || user?.username || '';
const displayName = user?.name?.en || user?.username || "";
const initials = displayName
? displayName.split(/\s+/).map((s) => s[0]).join('').toUpperCase().slice(0, 2)
: '?';
? displayName
.split(/\s+/)
.map((s) => s[0])
.join("")
.toUpperCase()
.slice(0, 2)
: "?";
const handleLogout = useCallback(() => {
dispatch(logout());
navigate('/login');
navigate("/login");
}, [dispatch, navigate]);
const segments = location.pathname.split('/').filter(Boolean);
const segments = location.pathname.split("/").filter(Boolean);
const crumbs = [
{ label: t('nav.dashboard'), path: '/dashboard' },
{ label: t("nav.dashboard"), path: "/dashboard" },
...segments
.map((_, i) => '/' + segments.slice(0, i + 1).join('/'))
.filter((path) => path !== '/dashboard')
.filter((path) => !path.startsWith('/um'))
.map((path) => ({ label: t('nav.dashboard'), path })),
.map((_, i) => "/" + segments.slice(0, i + 1).join("/"))
.filter((path) => path !== "/dashboard")
.filter((path) => !path.startsWith("/um"))
.map((path) => ({ label: t("nav.dashboard"), path })),
];
const go = (item: NavItem) => {
@@ -115,27 +175,31 @@ export function BackofficeLayout() {
setCollapsed((prev) => !prev);
}, []);
const isSidebar = layoutMode === 'sidebar';
const isSidebar = layoutMode === "sidebar";
return (
<AppShell
header={{ height: isSidebar ? 74 : HEADER_HEIGHT }}
navbar={isSidebar ? {
width: collapsed ? 72 : 264,
breakpoint: 'sm',
collapsed: { mobile: !opened },
} : undefined}
navbar={
isSidebar
? {
width: collapsed ? 72 : 264,
breakpoint: "sm",
collapsed: { mobile: !opened },
}
: undefined
}
padding="lg"
>
<AppShell.Header
style={{
background: 'var(--mantine-color-body)',
borderBottom: '1px solid var(--mantine-color-gray-2)',
display: 'flex',
flexDirection: 'column',
background: "var(--mantine-color-body)",
borderBottom: "1px solid var(--mantine-color-gray-2)",
display: "flex",
flexDirection: "column",
}}
>
<div style={{ height: 74, flexShrink: 0, padding: '0 32px' }}>
<div style={{ height: 74, flexShrink: 0, padding: "0 32px" }}>
<AppHeader
onToggleNav={toggleNav}
onToggleSidebar={isSidebar ? handleToggleCollapse : toggleNav}
@@ -143,7 +207,7 @@ export function BackofficeLayout() {
breadcrumbs={crumbs}
onNavigate={navigate}
onLogout={handleLogout}
userName={displayName || t('app.name')}
userName={displayName || t("app.name")}
userInitials={initials}
supportedLanguages={SUPPORTED_LANGUAGES}
/>
@@ -152,51 +216,58 @@ export function BackofficeLayout() {
{!isSidebar && (
<div
style={{
display: 'flex',
alignItems: 'center',
display: "flex",
alignItems: "center",
gap: rem(2),
padding: '0 32px',
padding: "0 32px",
height: 42,
borderTop: '1px solid var(--mantine-color-gray-1)',
overflowX: 'auto',
borderTop: "1px solid var(--mantine-color-gray-1)",
overflowX: "auto",
flexShrink: 0,
}}
>
{NAV_ITEMS.map((item) => {
const active = !!item.to && (location.pathname === item.to || location.pathname.startsWith(`${item.to}/`));
const active =
!!item.to &&
(location.pathname === item.to ||
location.pathname.startsWith(`${item.to}/`));
const ItemIcon = item.icon;
return (
<button
key={item.to}
onClick={() => go(item)}
style={{
display: 'flex',
alignItems: 'center',
display: "flex",
alignItems: "center",
gap: rem(6),
padding: '8px 16px',
border: 'none',
borderBottom: '2px solid',
padding: "8px 16px",
border: "none",
borderBottom: "2px solid",
borderBottomColor: active
? 'var(--mantine-color-blue-6)'
: 'transparent',
background: 'transparent',
? "var(--mantine-color-blue-6)"
: "transparent",
background: "transparent",
color: active
? 'var(--mantine-color-blue-6)'
: 'var(--mantine-color-gray-6)',
? "var(--mantine-color-blue-6)"
: "var(--mantine-color-gray-6)",
fontWeight: active ? 600 : 500,
fontSize: rem(14),
whiteSpace: 'nowrap',
cursor: 'pointer',
transition: 'all 150ms ease',
height: '100%',
whiteSpace: "nowrap",
cursor: "pointer",
transition: "all 150ms ease",
height: "100%",
marginBottom: -1,
fontFamily: 'inherit',
fontFamily: "inherit",
}}
onMouseEnter={(e) => {
if (!active) e.currentTarget.style.color = 'var(--mantine-color-blue-6)';
if (!active)
e.currentTarget.style.color =
"var(--mantine-color-blue-6)";
}}
onMouseLeave={(e) => {
if (!active) e.currentTarget.style.color = 'var(--mantine-color-gray-6)';
if (!active)
e.currentTarget.style.color =
"var(--mantine-color-gray-6)";
}}
>
<ItemIcon size={18} stroke={1.6} />
@@ -212,10 +283,10 @@ export function BackofficeLayout() {
<AppShell.Navbar
p={0}
style={{
overflow: 'hidden',
transition: 'width 200ms ease',
background: 'var(--mantine-color-body)',
borderRight: '1px solid var(--mantine-color-gray-2)',
overflow: "hidden",
transition: "width 200ms ease",
background: "var(--mantine-color-body)",
borderRight: "1px solid var(--mantine-color-gray-2)",
}}
>
<AppSidebar
@@ -224,8 +295,8 @@ export function BackofficeLayout() {
activePath={location.pathname}
onToggleCollapse={handleToggleCollapse}
onNavigate={go}
brandName={t('app.name')}
brandSubtitle={t('app.authority')}
brandName={t("app.name")}
brandSubtitle={t("app.authority")}
/>
</AppShell.Navbar>
)}

View File

@@ -30,9 +30,6 @@ import { QuestionPage } from '../features/question/pages/QuestionPage';
import { ExamPage } from '../features/exam/pages/ExamPage';
import { ExamDetailPage } from '../features/exam/pages/ExamDetailPage';
import { ResultPage } from '../features/result/pages/ResultPage';
import { VesselRegistrationQueuePage } from '../features/vessel-registration/pages/VesselRegistrationQueuePage';
import { VesselRegistrationReviewPage } from '../features/vessel-registration/pages/VesselRegistrationReviewPage';
import { VesselRegistrationReportPage } from '../features/vessel-registration/pages/VesselRegistrationReportPage';
import { VesselRegistrationFormBuilderPage } from '../features/vessel-registration/pages/VesselRegistrationFormBuilderPage';
import { VesselOwnershipTransferQueuePage } from '../features/vessel-registration/pages/VesselOwnershipTransferQueuePage';
import { VesselOwnershipTransferReviewPage } from '../features/vessel-registration/pages/VesselOwnershipTransferReviewPage';
@@ -50,6 +47,11 @@ import { MtoLicenseReviewPage } from '../features/mto-license/pages/MtoLicenseRe
import { WaiverQueuePage } from '../features/waiver/pages/WaiverQueuePage';
import { WaiverReviewPage } from '../features/waiver/pages/WaiverReviewPage';
import { LogisticsHeadDashboardPage } from '../features/logistics-head/pages/LogisticsHeadDashboardPage';
import { VesselTransferQueuePage } from '../features/vessel-transfer/pages/VesselTransferQueuePage';
import { VesselTransferReviewPage } from '../features/vessel-transfer/pages/VesselTransferReviewPage';
import { VesselRegistrationQueuePage } from '../features/vessel-registration/pages/VesselRegistrationQueuePage';
import { VesselRegistrationReviewPage } from '../features/vessel-registration/pages/VesselRegistrationReviewPage';
import { VesselRegistrationReportPage } from '../features/vessel-registration/pages/VesselRegistrationReportPage';
const router = createBrowserRouter([
{
@@ -86,9 +88,6 @@ const router = createBrowserRouter([
{ path: 'payment-config', element: <PaymentConfigPage /> },
{ path: 'seafarer-registry', element: <SeafarerRegistryPage /> },
{ path: 'seaman-book-queue', element: <SeamanBookQueuePage /> },
{ path: 'vessel-registrations', element: <VesselRegistrationQueuePage /> },
{ path: 'vessel-registrations/:id', element: <VesselRegistrationReviewPage /> },
{ path: 'vessel-registration-report', element: <VesselRegistrationReportPage /> },
{ path: 'vessel-transfers', element: <VesselTransferQueuePage /> },
{ path: 'vessel-transfers/:id', element: <VesselTransferReviewPage /> },
{ path: 'questions', element: <QuestionPage /> },