From f786357b6529cbc7df28aa0ca85ec556138db540 Mon Sep 17 00:00:00 2001 From: Rovina Marcus Lobo Date: Thu, 7 May 2026 16:58:52 +1000 Subject: [PATCH 1/2] Add multilingual UI support across employer panel --- app-frontend/employer-panel/src/App.js | 87 ++++++++---- .../employer-panel/src/components/Footer.js | 8 +- .../employer-panel/src/components/Header.js | 130 +++++++++++++++--- .../employer-panel/src/i18n/translations.js | 79 +++++++++++ .../src/pages/DailyMonitoring.js | 8 +- .../src/pages/EmployerDashboard.js | 10 +- .../employer-panel/src/pages/GuardProfile.js | 6 +- .../employer-panel/src/pages/ManageShift.js | 6 +- .../employer-panel/src/pages/Timesheet.js | 6 +- 9 files changed, 277 insertions(+), 63 deletions(-) create mode 100644 app-frontend/employer-panel/src/i18n/translations.js diff --git a/app-frontend/employer-panel/src/App.js b/app-frontend/employer-panel/src/App.js index c58748380..45ab1874a 100644 --- a/app-frontend/employer-panel/src/App.js +++ b/app-frontend/employer-panel/src/App.js @@ -1,5 +1,5 @@ import { BrowserRouter as Router, Routes, Route, useNavigate } from 'react-router-dom'; -import { useEffect } from 'react'; +import { useState, useEffect } from 'react'; import { attach401Handler } from './lib/http'; import ExpressionOfInterest from './pages/ExpressionOfInterest'; @@ -23,15 +23,12 @@ import PageTitleHandler from './components/PageTitleHandler'; import ProtectedRoute from './routes/ProtectedRoute'; -import Timesheet from "./pages/Timesheet"; +import Timesheet from './pages/Timesheet'; import DailyMonitoring from './pages/DailyMonitoring'; import Payroll from './pages/Payroll'; import PrivacyPolicy from './pages/PrivacyPolicy'; import TermsAndConditions from './pages/TermsAndConditions'; -/** - * PUBLIC ROUTE: Task Detail (no layout) - */ function TaskRoute() { return ( @@ -40,27 +37,30 @@ function TaskRoute() { ); } -/** - * PROTECTED LAYOUT WRAPPER - */ -function ProtectedLayout({ children }) { +function ProtectedLayout({ children, language, setLanguage }) { return (
-
+
{children}
-
+
); } -function AppRoutes() { +function AppRoutes({ language, setLanguage }) { const navigate = useNavigate(); useEffect(() => { attach401Handler(() => navigate('/login')); - }, []); + }, [navigate]); + + const protectedLayout = (children) => ( + + {children} + + ); return ( <> @@ -77,28 +77,61 @@ function AppRoutes() { } /> {/* PROTECTED ROUTES */} - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> + )} + /> + )} + /> + )} + /> + )} + /> + )} + /> + )} + /> + )} + /> + )} + /> + )} + />
); } -/** - * MAIN APP ROUTES - */ + function App() { + const [language, setLanguage] = useState( + localStorage.getItem('language') || 'en' + ); + + useEffect(() => { + localStorage.setItem('language', language); + }, [language]); + return ( - - + ); } -export default App; +export default App; \ No newline at end of file diff --git a/app-frontend/employer-panel/src/components/Footer.js b/app-frontend/employer-panel/src/components/Footer.js index ebbe21f92..c7bccde39 100644 --- a/app-frontend/employer-panel/src/components/Footer.js +++ b/app-frontend/employer-panel/src/components/Footer.js @@ -1,8 +1,10 @@ import React from 'react'; import CompanyLogo from './company_logo.svg'; import { Link } from 'react-router-dom'; +import translations from "../i18n/translations"; -export default function Footer() { +export default function Footer({ language }) { + const t = translations[language || "en"] || translations.en; const footerNavList = [ { title: 'Privacy Policy', link: '/privacy-policy' }, { title: 'Terms and Conditions', link: '/terms-and-condition' }, @@ -35,11 +37,11 @@ export default function Footer() { const buttonList = [ { - title: 'Expression of Interest', // fixed title + title: t.expressionInterest, // fixed title link: '/expression-of-interest', // ✅ now routes correctly }, { - title: 'Login', + title: t.login, link: '/login', }, ]; diff --git a/app-frontend/employer-panel/src/components/Header.js b/app-frontend/employer-panel/src/components/Header.js index 2eb4ef12e..d47258d69 100644 --- a/app-frontend/employer-panel/src/components/Header.js +++ b/app-frontend/employer-panel/src/components/Header.js @@ -3,8 +3,12 @@ import { Link, useNavigate } from 'react-router-dom'; import CompanyLogo from './company_logo.svg'; import ProfilePicPlaceHolder from './ProfilePicPlaceHolder.svg'; import NotificationsPopup from '../pages/NotificationsPopup'; +import translations from "../i18n/translations"; +import Logo from '../pages/logo.png'; + +export default function Header({ language, setLanguage }) { + const t = translations[language || "en"] || translations.en; -export default function Header() { const navigate = useNavigate(); const [showMenu, setShowMenu] = useState(false); @@ -48,25 +52,32 @@ export default function Header() { {/* Logo */}
Company Logo -
Secure Shift
+
+ Secure Shift +
{/* Navigation */}
+
- Home + {t.home}
- Shifts + {t.shifts} - Guard + {t.guard} + + + + {t.timesheet} - Activity + {t.dailyMonitoring} @@ -75,7 +86,7 @@ export default function Header() { {localStorage.getItem('userRole') === 'admin' && ( - Email + {t.email} )} @@ -83,7 +94,10 @@ export default function Header() { {/* Avatar + Dropdown */}
-
setShowMenu(!showMenu)} style={{ cursor: 'pointer' }}> +
setShowMenu(!showMenu)} + style={{ cursor: 'pointer' }} + > Profile + {/* Profile Section */}
{ - navigate('/company-profile'); - setShowMenu(false); + style={{ + display: 'flex', + alignItems: 'center', + gap: '12px', + padding: '16px', + borderBottom: '1px solid #eee', }} > - Profile + Profile + +
+
+ ABC Security +
+ +
+ {localStorage.getItem('email') || 'User'} +
+
+
+ + {/* Language Section */} +
+
+ 🌐 Language +
+ + {[ + { code: 'en', label: 'English' }, + { code: 'hi', label: 'Hindi' }, + { code: 'pa', label: 'Punjabi' }, + { code: 'zh', label: 'Chinese' }, + ].map((item) => ( +
setLanguage(item.code)} + style={{ + padding: '7px 0', + cursor: 'pointer', + fontSize: '14px', + color: + language === item.code ? '#274B93' : '#333', + fontWeight: + language === item.code ? '700' : '400', + }} + > + {item.label} +
+ ))}
+ {/* Logout */}
Logout
diff --git a/app-frontend/employer-panel/src/i18n/translations.js b/app-frontend/employer-panel/src/i18n/translations.js new file mode 100644 index 000000000..d59d0de96 --- /dev/null +++ b/app-frontend/employer-panel/src/i18n/translations.js @@ -0,0 +1,79 @@ +const translations = { + en: { + home: "Home", + shifts: "Shifts", + guard: "Guard", + timesheet: "Timesheet", + email: "Email", + overview: "Overview", + incidentReports: "Incident Reports", + manageShifts: "Manage Shifts", + guardProfiles: "Guard Profiles", + timesheets: "Timesheets", + dailyMonitoring: "Activity", + backDashboard: "Back to Dashboard", + searchGuard: "Search guard or site...", + recentReview: "Recent Review", + expressionInterest: "Expression of Interest", + login: "Login", + }, + + hi: { + home: "होम", + shifts: "शिफ्ट", + guard: "गार्ड", + timesheet: "टाइमशीट", + email: "ईमेल", + overview: "अवलोकन", + incidentReports: "घटना रिपोर्ट", + manageShifts: "शिफ्ट प्रबंधित करें", + guardProfiles: "गार्ड प्रोफाइल", + timesheets: "टाइमशीट", + dailyMonitoring: "गतिविधि", + backDashboard: "डैशबोर्ड पर वापस जाएं", + searchGuard: "गार्ड या साइट खोजें...", + recentReview: "हाल की समीक्षा", + expressionInterest: "रुचि की अभिव्यक्ति", + login: "लॉगिन", + }, + + pa: { + home: "ਹੋਮ", + shifts: "ਸ਼ਿਫਟਾਂ", + guard: "ਗਾਰਡ", + timesheet: "ਟਾਈਮਸ਼ੀਟ", + email: "ਈਮੇਲ", + overview: "ਸੰਖੇਪ", + incidentReports: "ਘਟਨਾ ਰਿਪੋਰਟਾਂ", + manageShifts: "ਸ਼ਿਫਟਾਂ ਸੰਭਾਲੋ", + guardProfiles: "ਗਾਰਡ ਪ੍ਰੋਫਾਈਲ", + timesheets: "ਟਾਈਮਸ਼ੀਟ", + dailyMonitoring: "ਗਤੀਵਿਧੀ", + backDashboard: "ਡੈਸ਼ਬੋਰਡ ਤੇ ਵਾਪਸ ਜਾਓ", + searchGuard: "ਗਾਰਡ ਜਾਂ ਸਾਈਟ ਖੋਜੋ...", + recentReview: "ਤਾਜ਼ਾ ਸਮੀਖਿਆ", + expressionInterest: "ਰੁਚੀ ਦਾ ਪ੍ਰਗਟਾਵਾ", + login: "ਲਾਗਇਨ", + }, + + zh: { + home: "首页", + shifts: "班次", + guard: "保安", + timesheet: "工时表", + email: "电子邮件", + overview: "概览", + incidentReports: "事件报告", + manageShifts: "管理班次", + guardProfiles: "保安档案", + timesheets: "工时表", + dailyMonitoring: "活动", + backDashboard: "返回仪表板", + searchGuard: "搜索保安或地点...", + recentReview: "最近评价", + expressionInterest: "意向表达", + login: "登录", + }, + }; + + export default translations; \ No newline at end of file diff --git a/app-frontend/employer-panel/src/pages/DailyMonitoring.js b/app-frontend/employer-panel/src/pages/DailyMonitoring.js index f62a8cce5..3f8b02928 100644 --- a/app-frontend/employer-panel/src/pages/DailyMonitoring.js +++ b/app-frontend/employer-panel/src/pages/DailyMonitoring.js @@ -1,6 +1,7 @@ import React, { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import './DailyMonitoring.css'; +import translations from '../i18n/translations'; const IconLocation = () => ( @@ -197,8 +198,9 @@ const statusBg = { 'On Shift': '#e1effe', Completed: '#def7ec', Absent: '#fde8e8 const filters = ['All', 'On Shift', 'Late', 'Absent', 'Completed']; -export default function DailyMonitoring() { +export default function DailyMonitoring({ language }) { const navigate = useNavigate(); + const t = translations[language || "en"] || translations.en; const [search, setSearch] = useState(''); const [filter, setFilter] = useState('All'); const [selected, setSelected] = useState(guards[0]); @@ -219,7 +221,7 @@ export default function DailyMonitoring() { return (
-

Daily Activity Monitoring

+

{t.dailyMonitoring}g

{stats.map((s) => ( @@ -381,7 +383,7 @@ export default function DailyMonitoring() { - Back to Dashboard + {t.backDashboard}
); diff --git a/app-frontend/employer-panel/src/pages/EmployerDashboard.js b/app-frontend/employer-panel/src/pages/EmployerDashboard.js index c9bd2ba85..1ddb45544 100644 --- a/app-frontend/employer-panel/src/pages/EmployerDashboard.js +++ b/app-frontend/employer-panel/src/pages/EmployerDashboard.js @@ -1,6 +1,7 @@ import React, { useMemo, useRef, useState, useEffect } from "react"; import { useNavigate } from "react-router-dom"; import "./EmployerDashboard.css"; +import translations from '../i18n/translations'; /* --- icons --- */ const IconCalendar = (props) => ( @@ -75,7 +76,8 @@ const parseIncidentDateTime = (incident) => { return baseDate.getTime(); }; -export default function EmployerDashboard() { +export default function EmployerDashboard({ language }) { + const t = translations[language || "en"] || translations.en; const [view, setView] = useState("list"); // default list view const overviewScroller = useRef(null); const reviewScroller = useRef(null); @@ -248,7 +250,7 @@ useEffect(() => { {/* -------- Overview -------- */}
-

Overview

+

{t.overview}

{/* Controls ABOVE grey grid */}
@@ -373,7 +375,7 @@ useEffect(() => {
{/* Incident Reports */} -

Incident Reports

+

{t.incidentReports}

{
{/* Reviews */} -

Recent Review

+

{t.recentReview}

diff --git a/app-frontend/employer-panel/src/pages/GuardProfile.js b/app-frontend/employer-panel/src/pages/GuardProfile.js index 1726a980a..2466563e6 100644 --- a/app-frontend/employer-panel/src/pages/GuardProfile.js +++ b/app-frontend/employer-panel/src/pages/GuardProfile.js @@ -1,5 +1,6 @@ import React, { useState, useRef, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; +import translations from "../i18n/translations"; // ❌ removed local dummy guardData @@ -22,7 +23,8 @@ const availabilityOptions = ['Available', 'Unavailable', 'On Leave']; const API_BASE = process.env.REACT_APP_API_BASE_URL || 'http://localhost:5000'; // NEW -function GuardProfiles() { +function GuardProfiles({ language }) { + const t = translations[language || "en"] || translations.en; const navigate = useNavigate(); const [currentPage, setCurrentPage] = useState(1); const [selectedSkills, setSelectedSkills] = useState([]); @@ -251,7 +253,7 @@ function GuardProfiles() { return (
-

Guard Profiles

+

{t.guardProfiles}

{/* NEW: loading / error / empty states */} {loading &&

Loading guards…

} {/* NEW */} {!loading && error /* NEW */ && ( diff --git a/app-frontend/employer-panel/src/pages/ManageShift.js b/app-frontend/employer-panel/src/pages/ManageShift.js index 5174add08..b4c23e044 100644 --- a/app-frontend/employer-panel/src/pages/ManageShift.js +++ b/app-frontend/employer-panel/src/pages/ManageShift.js @@ -1,6 +1,7 @@ import React, { useState, useEffect, useRef } from 'react'; import { useNavigate } from 'react-router-dom'; import http from '../lib/http'; +import translations from "../i18n/translations"; // Map backend status to filter display const statusDisplayMap = { @@ -69,7 +70,8 @@ const normalizeShift = (s) => ({ assignedGuard: s.assignedGuard || s.acceptedBy || null, }); -const ManageShift = () => { +const ManageShift = ({ language }) => { + const t = translations[language || "en"] || translations.en; const navigate = useNavigate(); const [shifts, setShifts] = useState([]); const [loading, setLoading] = useState(true); @@ -452,7 +454,7 @@ const ManageShift = () => { return (
-

Manage Shifts

+

{t.manageShifts}

diff --git a/app-frontend/employer-panel/src/pages/Timesheet.js b/app-frontend/employer-panel/src/pages/Timesheet.js index 2e3341761..837bf736c 100644 --- a/app-frontend/employer-panel/src/pages/Timesheet.js +++ b/app-frontend/employer-panel/src/pages/Timesheet.js @@ -2,6 +2,7 @@ import React, { useEffect, useMemo, useState } from "react"; import { useNavigate } from "react-router-dom"; import http from "../lib/http"; import "./Timesheet.css"; +import translations from "../i18n/translations"; const LATE_GRACE_MINUTES = 5; const PAGE_SIZE = 10; @@ -90,7 +91,8 @@ function buildRows(shifts, attendanceLists) { }); } -export default function Timesheet() { +export default function Timesheet({ language }) { + const t = translations[language || "en"] || translations.en; const navigate = useNavigate(); const [rows, setRows] = useState([]); const [loading, setLoading] = useState(true); @@ -183,7 +185,7 @@ export default function Timesheet() { return (
-

Timesheets

+

{t.timesheets}

{summary.map((card) => ( From 91acb0fbfd224429a32f67262bc5c3500e5f321a Mon Sep 17 00:00:00 2001 From: Rovina Marcus Lobo Date: Sat, 9 May 2026 15:28:01 +1000 Subject: [PATCH 2/2] Fix header translation conflicts and remove duplicate timesheet button --- app-frontend/employer-panel/src/components/Header.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/app-frontend/employer-panel/src/components/Header.js b/app-frontend/employer-panel/src/components/Header.js index d47258d69..823f5a535 100644 --- a/app-frontend/employer-panel/src/components/Header.js +++ b/app-frontend/employer-panel/src/components/Header.js @@ -80,9 +80,6 @@ export default function Header({ language, setLanguage }) { {t.dailyMonitoring} - - Timesheet - {localStorage.getItem('userRole') === 'admin' && (