From e63e6a2fc081ec6c8238f9f8b8bc086094bfc0f3 Mon Sep 17 00:00:00 2001 From: natib21 Date: Tue, 14 Jul 2026 08:47:46 +0000 Subject: [PATCH] fix ui --- .../backoffice/src/pages/ArchiveUsersPage.tsx | 2 +- .../src/pages/ArchivedUnitsPositionsPage.tsx | 6 +- .../src/pages/OrgAdminDashboard.tsx | 10 +-- .../src/user-management/AppMenuTabs.tsx | 5 +- .../src/user-management/Applayout.tsx | 6 +- .../src/filters/http-exception.filter.ts | 79 ++++++++++++++++++- 6 files changed, 92 insertions(+), 16 deletions(-) diff --git a/apps/edr-freight-web/backoffice/src/pages/ArchiveUsersPage.tsx b/apps/edr-freight-web/backoffice/src/pages/ArchiveUsersPage.tsx index 93d68b90d..f8600afd5 100644 --- a/apps/edr-freight-web/backoffice/src/pages/ArchiveUsersPage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/ArchiveUsersPage.tsx @@ -71,7 +71,7 @@ const ArchiveUsersPage = () => { value={selectedUnitId} onValueChange={(value) => setSelectedUnitId(value)} > - + diff --git a/apps/edr-freight-web/backoffice/src/pages/ArchivedUnitsPositionsPage.tsx b/apps/edr-freight-web/backoffice/src/pages/ArchivedUnitsPositionsPage.tsx index f6bccd67f..49f1e5c07 100644 --- a/apps/edr-freight-web/backoffice/src/pages/ArchivedUnitsPositionsPage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/ArchivedUnitsPositionsPage.tsx @@ -203,8 +203,8 @@ const ArchivedUnitsPositionsPage = () => { header: t("organization.name", "Name"), cell: ({ row }: any) => (
-
- +
+
@@ -397,7 +397,7 @@ const ArchivedUnitsPositionsPage = () => { ) : ( <> - + {t("archive.archivedPositions", "Archived Positions")} )} diff --git a/apps/edr-freight-web/backoffice/src/pages/OrgAdminDashboard.tsx b/apps/edr-freight-web/backoffice/src/pages/OrgAdminDashboard.tsx index 34c2fa413..cbe469a46 100644 --- a/apps/edr-freight-web/backoffice/src/pages/OrgAdminDashboard.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/OrgAdminDashboard.tsx @@ -118,7 +118,7 @@ const OrgAdminDashboard = () => { title: t("dashboard.totalEmployees"), value: activeReport?.employeesCount?.toLocaleString() || "0", icon: Users, - color: "from-blue-500 to-blue-600", + color: "from-primary-500 to-primary-600", }, ...(!isUnitAdmin ? [{ @@ -147,7 +147,7 @@ const OrgAdminDashboard = () => { description: t("dashboard.userManagementDesc"), icon: Users, action: () => navigate("/user-management/user_management"), - color: "bg-gradient-to-r from-blue-500 to-cyan-600", + color: "bg-gradient-to-r from-primary-500 to-primary-600", }, { id: "content-mgmt", @@ -155,7 +155,7 @@ const OrgAdminDashboard = () => { description: t("dashboard.contentManagementDesc"), icon: Files, action: () => navigate("/user-management/content-management"), - color: "bg-gradient-to-r from-purple-500 to-indigo-600", + color: "bg-gradient-to-r from-purple-500 to-primary-600", }, { id: "excel-upload", @@ -187,7 +187,7 @@ const OrgAdminDashboard = () => { description: t("dashboard.webManagementDesc"), icon: FileText, action: () => navigate("/user-management/web-management"), - color: "bg-gradient-to-r from-teal-500 to-cyan-600", + color: "bg-gradient-to-r from-teal-500 to-primary-600", }, ]; @@ -278,7 +278,7 @@ const OrgAdminDashboard = () => { : getStats().map((stat) => (
diff --git a/apps/edr-freight-web/backoffice/src/user-management/AppMenuTabs.tsx b/apps/edr-freight-web/backoffice/src/user-management/AppMenuTabs.tsx index 24d6c41b6..4c38d894c 100644 --- a/apps/edr-freight-web/backoffice/src/user-management/AppMenuTabs.tsx +++ b/apps/edr-freight-web/backoffice/src/user-management/AppMenuTabs.tsx @@ -234,7 +234,10 @@ export const AppMenuTabs = () => { ); return ( -
+ // Sticky (not fixed) so it stays in flow: content below never needs a + // magic offset matching this bar's responsive height. top-16 keeps it + // pinned just below the fixed 64px header while scrolling. +
{/* Mobile View - Two separate rows */}
{/* Primary items row */} diff --git a/apps/edr-freight-web/backoffice/src/user-management/Applayout.tsx b/apps/edr-freight-web/backoffice/src/user-management/Applayout.tsx index 0951337e4..7a7e56b76 100644 --- a/apps/edr-freight-web/backoffice/src/user-management/Applayout.tsx +++ b/apps/edr-freight-web/backoffice/src/user-management/Applayout.tsx @@ -27,10 +27,12 @@ export const AppLayout = () => { } return ( -
+ // pt-16 clears the fixed 64px ; AppMenuTabs is sticky and in flow, + // so content starts right below it at any tabs height (mobile/desktop). +
-
+
diff --git a/packages/api-common/src/filters/http-exception.filter.ts b/packages/api-common/src/filters/http-exception.filter.ts index c25e551bf..3dc301a0b 100644 --- a/packages/api-common/src/filters/http-exception.filter.ts +++ b/packages/api-common/src/filters/http-exception.filter.ts @@ -16,6 +16,67 @@ interface ErrorResponseBody { path: string; } +interface PgDriverError { + code?: string; + detail?: string; + column?: string; + message?: string; +} + +const toSentenceCase = (snake: string): string => + snake.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()); + +/** + * Translate common Postgres constraint failures (surfaced by TypeORM as + * QueryFailedError, which is NOT an HttpException and would otherwise fall + * through as an opaque 500) into actionable 400s. Clients rely on the + * "still referenced" wording to show friendly cannot-delete messages. + */ +const translatePgError = ( + driver: PgDriverError, +): { status: number; message: string } | null => { + const detail = driver.detail ?? ""; + switch (driver.code) { + case "23503": { + const stillReferenced = detail.match( + /is still referenced from table "(.*?)"/, + ); + if (stillReferenced) { + const table = toSentenceCase(stillReferenced[1]); + return { + status: HttpStatus.BAD_REQUEST, + message: `Cannot delete: this record is still referenced by ${table}.`, + }; + } + const notPresent = detail.match(/is not present in table "(.*?)"/); + const entity = toSentenceCase(notPresent?.[1] ?? "referenced entity"); + return { + status: HttpStatus.BAD_REQUEST, + message: `The specified ${entity} does not exist.`, + }; + } + case "23505": { + const pair = detail.match(/\((.*?)\)=\((.*?)\)/); + return { + status: HttpStatus.BAD_REQUEST, + message: `Duplicate entry: '${pair?.[2] ?? "value"}' already exists for '${toSentenceCase(pair?.[1] ?? "field")}'.`, + }; + } + case "23502": + return { + status: HttpStatus.BAD_REQUEST, + message: `Missing required field: ${toSentenceCase(driver.column ?? "field")}.`, + }; + case "22P02": + return { + status: HttpStatus.BAD_REQUEST, + message: "Invalid input format (e.g., wrong UUID or number).", + }; + default: + return null; + } +}; + @Catch() export class HttpExceptionFilter implements ExceptionFilter { private readonly logger = new Logger(HttpExceptionFilter.name); @@ -25,13 +86,23 @@ export class HttpExceptionFilter implements ExceptionFilter { const response = ctx.getResponse(); const request = ctx.getRequest(); - const status = - exception instanceof HttpException + const pgTranslated = + (exception as Error)?.name === "QueryFailedError" + ? translatePgError( + ((exception as { driverError?: PgDriverError }).driverError ?? + {}) as PgDriverError, + ) + : null; + + const status = pgTranslated + ? pgTranslated.status + : exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR; - const messageRaw = - exception instanceof HttpException + const messageRaw = pgTranslated + ? pgTranslated.message + : exception instanceof HttpException ? exception.getResponse() : "Internal server error";