From fac35319e5b75f57759c2e09fd5bdf4db2e64a34 Mon Sep 17 00:00:00 2001 From: Arthur Bassi Date: Fri, 12 Jun 2026 11:57:07 -0300 Subject: [PATCH] chore: remove legacy CRA pages, Redux store and JS hooks --- src/App.js | 179 --- src/App.test.js | 8 - src/Site.Layout.js | 45 - src/apiUtil.js | 76 - src/app/slices/authSlice.js | 79 -- src/app/slices/uiSlice.js | 54 - src/app/store.js | 19 - src/components/ActionBar.css | 122 -- src/components/ActionBar.js | 164 --- src/components/DeleteModal.js | 141 -- src/components/SharedTable.js | 284 ---- src/components/Sidebar.js | 111 -- src/components/SignatureCapture.css | 125 -- src/components/SignatureCapture.js | 123 -- src/components/Topbar.js | 142 -- src/constants/actionTypes.js | 16 - src/constants/index.js | 87 -- src/constants/queryKeys.js | 82 -- src/hooks/api/useEmployees.js | 85 -- src/hooks/api/usePMSchedules.js | 69 - src/hooks/api/useWorkOrders.js | 91 -- src/hooks/useAuth.js | 75 - src/hooks/useDebounce.js | 22 - src/hooks/useModal.js | 33 - src/hooks/usePaginatedList.js | 45 - src/index.css | 569 -------- src/index.html | 1 - src/index.js | 21 - src/lib/api/client.js | 79 -- src/lib/api/services.js | 268 ---- src/lib/queryClient.js | 25 - src/pages/Dashboard.css | 122 -- src/pages/Dashboard.js | 131 -- src/pages/PmSchedule/api.js | 41 - src/pages/PmSchedule/edit/api.js | 6 - .../PmSchedule/form/PmScheduleFormPage.css | 289 ---- .../PmSchedule/form/PmScheduleFormPage.js | 303 ---- src/pages/PmSchedule/list/Delete.js | 22 - src/pages/PmSchedule/list/Form.js | 0 src/pages/PmSchedule/list/List.js | 161 --- src/pages/PmSchedule/list/Table.js | 17 - src/pages/PmSchedule/list/api.js | 38 - src/pages/PmSchedule/list/styles.css | 231 --- src/pages/PmSchedule/new/api.js | 11 - src/pages/accounts/api.js | 49 - src/pages/accounts/edit/api.js | 5 - src/pages/accounts/form/AccountFormPage.js | 239 ---- src/pages/accounts/list/Delete.js | 22 - src/pages/accounts/list/Form.js | 0 src/pages/accounts/list/List.js | 138 -- src/pages/accounts/list/Table.js | 30 - src/pages/accounts/list/api.js | 26 - src/pages/accounts/list/styles.css | 231 --- src/pages/accounts/new/api.js | 5 - src/pages/assets/form/AssetFormPage.js | 290 ---- src/pages/assets/list/Delete.js | 36 - src/pages/assets/list/List.js | 133 -- src/pages/assets/list/Table.js | 88 -- src/pages/assets/list/api.js | 28 - src/pages/assets/list/useItems.js | 63 - src/pages/auth/LoginPage.css | 190 --- src/pages/auth/LoginPage.js | 135 -- src/pages/auth/LoginPage.module.css | 188 --- src/pages/calendar/api.js | 115 -- .../calendar/components/CalendarActionBar.css | 142 -- .../calendar/components/CalendarActionBar.js | 185 --- src/pages/calendar/form/EventFormPage.css | 344 ----- src/pages/calendar/form/EventFormPage.js | 456 ------ src/pages/calendar/list/CalendarPage.css | 178 --- src/pages/calendar/list/CalendarPage.js | 250 ---- src/pages/calendar/list/Table.js | 163 --- src/pages/calendar/list/useEvents.js | 48 - src/pages/contacts/edit/api.js | 4 - src/pages/contacts/form/ContactFormPage.js | 673 --------- src/pages/contacts/list/Delete.js | 22 - src/pages/contacts/list/Form.js | 0 src/pages/contacts/list/List.js | 112 -- src/pages/contacts/list/Table.js | 31 - src/pages/contacts/list/api.js | 27 - src/pages/contacts/list/styles.css | 231 --- src/pages/contacts/new/api.js | 4 - src/pages/dashboard/Dashboard.css | 70 - src/pages/dashboard/Dashboard.js | 130 -- src/pages/dashboard/api.js | 29 - src/pages/employees/api.js | 131 -- src/pages/employees/edit/api.js | 6 - src/pages/employees/form/ContactFormPage.css | 180 --- src/pages/employees/form/EmployeeFormPage.css | 418 ------ src/pages/employees/form/EmployeeFormPage.js | 682 --------- src/pages/employees/list/Delete.js | 22 - src/pages/employees/list/Form.js | 0 src/pages/employees/list/List.js | 138 -- src/pages/employees/list/Table.js | 32 - src/pages/employees/list/api.js | 25 - src/pages/employees/list/styles.css | 231 --- src/pages/employees/new/api.js | 6 - src/pages/followup/api.js | 20 - src/pages/followup/edit/api.js | 6 - src/pages/followup/form/FollowUpFormPage.css | 289 ---- src/pages/followup/form/FollowUpFormPage.js | 343 ----- src/pages/followup/list/Delete.js | 22 - src/pages/followup/list/Form.js | 0 src/pages/followup/list/List.js | 107 -- src/pages/followup/list/Table.js | 77 - src/pages/followup/list/api.js | 42 - src/pages/followup/list/styles.css | 353 ----- src/pages/followup/new/api.js | 43 - src/pages/locations/api.js | 49 - src/pages/locations/form/LocationFormPage.css | 289 ---- src/pages/locations/form/LocationFormPage.js | 321 ----- src/pages/locations/list/List.js | 157 -- src/pages/locations/list/Table.js | 18 - src/pages/locations/list/api.js | 30 - src/pages/locations/list/styles.css | 231 --- src/pages/settings/DropdownOptionsPage.css | 182 --- src/pages/settings/DropdownOptionsPage.js | 195 --- src/pages/settings/SettingsNav.js | 23 - src/pages/settings/TaskTemplatesPage.css | 263 ---- src/pages/settings/TaskTemplatesPage.js | 203 --- src/pages/uplifts/UpliftQueuePage.css | 135 -- src/pages/uplifts/UpliftQueuePage.js | 182 --- src/pages/user/api.js | 5 - src/pages/vendor-portal/DashboardPage.js | 90 -- src/pages/vendor-portal/DispatchDetailPage.js | 488 ------- src/pages/vendor-portal/PosPage.js | 144 -- src/pages/vendor-portal/SignaturePad.js | 87 -- .../vendor-portal/VendorPortalContext.js | 41 - src/pages/vendor-portal/VendorPortalLayout.js | 78 - src/pages/vendor-portal/api.js | 41 - src/pages/vendor-portal/statusHelpers.js | 18 - src/pages/vendor-portal/vendor-portal.css | 375 ----- src/pages/vendor-pos/list/List.js | 110 -- src/pages/vendor-pos/list/Table.js | 91 -- src/pages/vendor-pos/list/api.js | 31 - src/pages/vendor-pos/list/styles.css | 111 -- .../vendor-pos/view/VendorPoViewPage.css | 297 ---- src/pages/vendor-pos/view/VendorPoViewPage.js | 363 ----- src/pages/vendors/form/VendorFormPage.css | 81 -- src/pages/vendors/form/VendorFormPage.js | 239 ---- .../vendors/form/VendorPortalTokenPanel.js | 103 -- src/pages/vendors/list/List.js | 82 -- src/pages/vendors/list/Table.js | 17 - src/pages/vendors/list/api.js | 25 - src/pages/vendors/list/styles.css | 63 - src/pages/workorders/api.js | 184 --- .../workorders/form/WorkOrderFormPage.css | 297 ---- .../workorders/form/WorkOrderFormPage.js | 341 ----- .../form/WorkOrderFormPage.js.backup | 262 ---- src/pages/workorders/list/List.js | 134 -- src/pages/workorders/list/Table.js | 199 --- src/pages/workorders/list/api.js | 34 - src/pages/workorders/list/styles.css | 450 ------ .../workorders/view/WorkOrderViewPage.css | 1122 --------------- .../workorders/view/WorkOrderViewPage.js | 1264 ----------------- src/reportWebVitals.js | 13 - src/services/api.js | 168 --- src/services/authService.js | 88 -- src/setupProxy.js | 14 - src/setupTests.js | 5 - src/site.css | 680 --------- src/tokenUtility.js | 50 - 161 files changed, 23458 deletions(-) delete mode 100644 src/App.js delete mode 100644 src/App.test.js delete mode 100644 src/Site.Layout.js delete mode 100644 src/apiUtil.js delete mode 100644 src/app/slices/authSlice.js delete mode 100644 src/app/slices/uiSlice.js delete mode 100644 src/app/store.js delete mode 100644 src/components/ActionBar.css delete mode 100644 src/components/ActionBar.js delete mode 100644 src/components/DeleteModal.js delete mode 100644 src/components/SharedTable.js delete mode 100644 src/components/Sidebar.js delete mode 100644 src/components/SignatureCapture.css delete mode 100644 src/components/SignatureCapture.js delete mode 100644 src/components/Topbar.js delete mode 100644 src/constants/actionTypes.js delete mode 100644 src/constants/index.js delete mode 100644 src/constants/queryKeys.js delete mode 100644 src/hooks/api/useEmployees.js delete mode 100644 src/hooks/api/usePMSchedules.js delete mode 100644 src/hooks/api/useWorkOrders.js delete mode 100644 src/hooks/useAuth.js delete mode 100644 src/hooks/useDebounce.js delete mode 100644 src/hooks/useModal.js delete mode 100644 src/hooks/usePaginatedList.js delete mode 100644 src/index.css delete mode 100644 src/index.html delete mode 100644 src/index.js delete mode 100644 src/lib/api/client.js delete mode 100644 src/lib/api/services.js delete mode 100644 src/lib/queryClient.js delete mode 100644 src/pages/Dashboard.css delete mode 100644 src/pages/Dashboard.js delete mode 100644 src/pages/PmSchedule/api.js delete mode 100644 src/pages/PmSchedule/edit/api.js delete mode 100644 src/pages/PmSchedule/form/PmScheduleFormPage.css delete mode 100644 src/pages/PmSchedule/form/PmScheduleFormPage.js delete mode 100644 src/pages/PmSchedule/list/Delete.js delete mode 100644 src/pages/PmSchedule/list/Form.js delete mode 100644 src/pages/PmSchedule/list/List.js delete mode 100644 src/pages/PmSchedule/list/Table.js delete mode 100644 src/pages/PmSchedule/list/api.js delete mode 100644 src/pages/PmSchedule/list/styles.css delete mode 100644 src/pages/PmSchedule/new/api.js delete mode 100644 src/pages/accounts/api.js delete mode 100644 src/pages/accounts/edit/api.js delete mode 100644 src/pages/accounts/form/AccountFormPage.js delete mode 100644 src/pages/accounts/list/Delete.js delete mode 100644 src/pages/accounts/list/Form.js delete mode 100644 src/pages/accounts/list/List.js delete mode 100644 src/pages/accounts/list/Table.js delete mode 100644 src/pages/accounts/list/api.js delete mode 100644 src/pages/accounts/list/styles.css delete mode 100644 src/pages/accounts/new/api.js delete mode 100644 src/pages/assets/form/AssetFormPage.js delete mode 100644 src/pages/assets/list/Delete.js delete mode 100644 src/pages/assets/list/List.js delete mode 100644 src/pages/assets/list/Table.js delete mode 100644 src/pages/assets/list/api.js delete mode 100644 src/pages/assets/list/useItems.js delete mode 100644 src/pages/auth/LoginPage.css delete mode 100644 src/pages/auth/LoginPage.js delete mode 100644 src/pages/auth/LoginPage.module.css delete mode 100644 src/pages/calendar/api.js delete mode 100644 src/pages/calendar/components/CalendarActionBar.css delete mode 100644 src/pages/calendar/components/CalendarActionBar.js delete mode 100644 src/pages/calendar/form/EventFormPage.css delete mode 100644 src/pages/calendar/form/EventFormPage.js delete mode 100644 src/pages/calendar/list/CalendarPage.css delete mode 100644 src/pages/calendar/list/CalendarPage.js delete mode 100644 src/pages/calendar/list/Table.js delete mode 100644 src/pages/calendar/list/useEvents.js delete mode 100644 src/pages/contacts/edit/api.js delete mode 100644 src/pages/contacts/form/ContactFormPage.js delete mode 100644 src/pages/contacts/list/Delete.js delete mode 100644 src/pages/contacts/list/Form.js delete mode 100644 src/pages/contacts/list/List.js delete mode 100644 src/pages/contacts/list/Table.js delete mode 100644 src/pages/contacts/list/api.js delete mode 100644 src/pages/contacts/list/styles.css delete mode 100644 src/pages/contacts/new/api.js delete mode 100644 src/pages/dashboard/Dashboard.css delete mode 100644 src/pages/dashboard/Dashboard.js delete mode 100644 src/pages/dashboard/api.js delete mode 100644 src/pages/employees/api.js delete mode 100644 src/pages/employees/edit/api.js delete mode 100644 src/pages/employees/form/ContactFormPage.css delete mode 100644 src/pages/employees/form/EmployeeFormPage.css delete mode 100644 src/pages/employees/form/EmployeeFormPage.js delete mode 100644 src/pages/employees/list/Delete.js delete mode 100644 src/pages/employees/list/Form.js delete mode 100644 src/pages/employees/list/List.js delete mode 100644 src/pages/employees/list/Table.js delete mode 100644 src/pages/employees/list/api.js delete mode 100644 src/pages/employees/list/styles.css delete mode 100644 src/pages/employees/new/api.js delete mode 100644 src/pages/followup/api.js delete mode 100644 src/pages/followup/edit/api.js delete mode 100644 src/pages/followup/form/FollowUpFormPage.css delete mode 100644 src/pages/followup/form/FollowUpFormPage.js delete mode 100644 src/pages/followup/list/Delete.js delete mode 100644 src/pages/followup/list/Form.js delete mode 100644 src/pages/followup/list/List.js delete mode 100644 src/pages/followup/list/Table.js delete mode 100644 src/pages/followup/list/api.js delete mode 100644 src/pages/followup/list/styles.css delete mode 100644 src/pages/followup/new/api.js delete mode 100644 src/pages/locations/api.js delete mode 100644 src/pages/locations/form/LocationFormPage.css delete mode 100644 src/pages/locations/form/LocationFormPage.js delete mode 100644 src/pages/locations/list/List.js delete mode 100644 src/pages/locations/list/Table.js delete mode 100644 src/pages/locations/list/api.js delete mode 100644 src/pages/locations/list/styles.css delete mode 100644 src/pages/settings/DropdownOptionsPage.css delete mode 100644 src/pages/settings/DropdownOptionsPage.js delete mode 100644 src/pages/settings/SettingsNav.js delete mode 100644 src/pages/settings/TaskTemplatesPage.css delete mode 100644 src/pages/settings/TaskTemplatesPage.js delete mode 100644 src/pages/uplifts/UpliftQueuePage.css delete mode 100644 src/pages/uplifts/UpliftQueuePage.js delete mode 100644 src/pages/user/api.js delete mode 100644 src/pages/vendor-portal/DashboardPage.js delete mode 100644 src/pages/vendor-portal/DispatchDetailPage.js delete mode 100644 src/pages/vendor-portal/PosPage.js delete mode 100644 src/pages/vendor-portal/SignaturePad.js delete mode 100644 src/pages/vendor-portal/VendorPortalContext.js delete mode 100644 src/pages/vendor-portal/VendorPortalLayout.js delete mode 100644 src/pages/vendor-portal/api.js delete mode 100644 src/pages/vendor-portal/statusHelpers.js delete mode 100644 src/pages/vendor-portal/vendor-portal.css delete mode 100644 src/pages/vendor-pos/list/List.js delete mode 100644 src/pages/vendor-pos/list/Table.js delete mode 100644 src/pages/vendor-pos/list/api.js delete mode 100644 src/pages/vendor-pos/list/styles.css delete mode 100644 src/pages/vendor-pos/view/VendorPoViewPage.css delete mode 100644 src/pages/vendor-pos/view/VendorPoViewPage.js delete mode 100644 src/pages/vendors/form/VendorFormPage.css delete mode 100644 src/pages/vendors/form/VendorFormPage.js delete mode 100644 src/pages/vendors/form/VendorPortalTokenPanel.js delete mode 100644 src/pages/vendors/list/List.js delete mode 100644 src/pages/vendors/list/Table.js delete mode 100644 src/pages/vendors/list/api.js delete mode 100644 src/pages/vendors/list/styles.css delete mode 100644 src/pages/workorders/api.js delete mode 100644 src/pages/workorders/form/WorkOrderFormPage.css delete mode 100644 src/pages/workorders/form/WorkOrderFormPage.js delete mode 100644 src/pages/workorders/form/WorkOrderFormPage.js.backup delete mode 100644 src/pages/workorders/list/List.js delete mode 100644 src/pages/workorders/list/Table.js delete mode 100644 src/pages/workorders/list/api.js delete mode 100644 src/pages/workorders/list/styles.css delete mode 100644 src/pages/workorders/view/WorkOrderViewPage.css delete mode 100644 src/pages/workorders/view/WorkOrderViewPage.js delete mode 100644 src/reportWebVitals.js delete mode 100644 src/services/api.js delete mode 100644 src/services/authService.js delete mode 100644 src/setupProxy.js delete mode 100644 src/setupTests.js delete mode 100644 src/site.css delete mode 100644 src/tokenUtility.js diff --git a/src/App.js b/src/App.js deleted file mode 100644 index 09ca4da0..00000000 --- a/src/App.js +++ /dev/null @@ -1,179 +0,0 @@ -import React, { useState } from "react"; -import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"; - -import Sidebar from "./components/Sidebar"; -import Topbar from "./components/Topbar"; -import Dashboard from "./pages/Dashboard"; - -// AUTH -import LoginPage from "./pages/auth/LoginPage"; -import { isAuthenticated } from "./services/authService"; - -// CONTACTS -import ContactsList from "./pages/contacts/list/List"; -import ContactFormPage from "./pages/contacts/form/ContactFormPage"; - -// ACCOUNTS -import AccountList from "./pages/accounts/list/List"; -import AccountFormPage from "./pages/accounts/form/AccountFormPage"; - -// EMPLOYEES -import EmployeeList from "./pages/employees/list/List"; -import EmployeeFormPage from "./pages/employees/form/EmployeeFormPage"; - -// LOCATIONS -import LocationsList from "./pages/locations/list/List"; -import LocationFormPage from "./pages/locations/form/LocationFormPage"; - -// WORK ORDERS -import WorkOrdersList from "./pages/workorders/list/List"; -import WorkOrderViewPage from "./pages/workorders/view/WorkOrderViewPage"; -import WorkOrderFormPage from "./pages/workorders/form/WorkOrderFormPage"; - -// FOLLOW UPS -import FollowUpList from "./pages/followup/list/List"; -import FollowUpFormPage from "./pages/followup/form/FollowUpFormPage"; - -// PM SCHEDULES -import PMScheduleList from "./pages/PmSchedule/list/List"; -import PMScheduleFormPage from "./pages/PmSchedule/form/PmScheduleFormPage"; - -// ASSETS -import AssetsList from "./pages/assets/list/List"; -import AssetFormPage from "./pages/assets/form/AssetFormPage"; - -// CALENDAR -import CalendarPage from "./pages/calendar/list/CalendarPage"; -import EventFormPage from "./pages/calendar/form/EventFormPage"; - -// VENDORS -import VendorsList from "./pages/vendors/list/List"; -import VendorFormPage from "./pages/vendors/form/VendorFormPage"; - -// UPLIFTS -import UpliftQueuePage from "./pages/uplifts/UpliftQueuePage"; - -// VENDOR POs -import VendorPoList from "./pages/vendor-pos/list/List"; -import VendorPoViewPage from "./pages/vendor-pos/view/VendorPoViewPage"; - -// SETTINGS -import DropdownOptionsPage from "./pages/settings/DropdownOptionsPage"; -import TaskTemplatesPage from "./pages/settings/TaskTemplatesPage"; - -// VENDOR PORTAL (public, token-authenticated, separate tree) -import VendorPortalLayout from "./pages/vendor-portal/VendorPortalLayout"; -import VendorPortalDashboard from "./pages/vendor-portal/DashboardPage"; -import VendorPortalPos from "./pages/vendor-portal/PosPage"; -import VendorPortalDispatchDetail from "./pages/vendor-portal/DispatchDetailPage"; - -// Protected Route Component -function ProtectedRoute({ children }) { - return isAuthenticated() ? children : ; -} - -export default function App() { - const [navCollapsed, setNavCollapsed] = useState(false); - - return ( - - - {/* Public Routes */} - } /> - - {/* Vendor Portal — public, token-authenticated via URL */} - }> - } /> - } /> - } /> - } /> - - - {/* Protected Routes */} - -
- - -
- setNavCollapsed(v => !v)} /> - -
- - } /> - } /> - - {/* CONTACTS */} - } /> - } /> - } /> - - {/* ACCOUNTS */} - } /> - } /> - } /> - - {/* EMPLOYEES */} - } /> - } /> - } /> - - {/* LOCATIONS */} - } /> - } /> - } /> - - {/* WORK ORDERS */} - } /> - } /> - } /> - } /> - - {/* FOLLOW UPS */} - } /> - } /> - } /> - - {/* PM SCHEDULES */} - } /> - } /> - } /> - - {/* ASSETS */} - } /> - } /> - } /> - - {/* VENDORS */} - } /> - } /> - } /> - - {/* CALENDAR */} - } /> - } /> - } /> - - {/* UPLIFTS */} - } /> - - {/* VENDOR POs */} - } /> - } /> - - {/* SETTINGS */} - } /> - } /> - -
-
-
- - } - /> -
-
- ); -} diff --git a/src/App.test.js b/src/App.test.js deleted file mode 100644 index 1f03afee..00000000 --- a/src/App.test.js +++ /dev/null @@ -1,8 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import App from './App'; - -test('renders learn react link', () => { - render(); - const linkElement = screen.getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); -}); diff --git a/src/Site.Layout.js b/src/Site.Layout.js deleted file mode 100644 index 3b0293d1..00000000 --- a/src/Site.Layout.js +++ /dev/null @@ -1,45 +0,0 @@ -import React from "react"; -import { Container } from "@mui/material"; -import { ThemeProvider, createTheme } from "@mui/material/styles"; -import "./site.css"; -import TopNav from "./components/TopNav"; -import LeftNav from "./components/LeftNav"; -import Main from "./components/Main"; - -const THEME = createTheme({ - typography: { - fontFamily: `"Dosis", "Nunito", sans-serif`, - fontSize: 14, - fontWeightLight: 300, - fontWeightRegular: 400, - fontWeightMedium: 500, - }, - palette: { - header: { - main: "#4D81B7", - contrastText: "header background", - }, - }, - flexBox: { - display: "flex", - }, -}); - -const Layout = (props) => { - const [open, setOpen] = React.useState(false); - - const handleDrawerOpen = () => { - setOpen(true); - }; - - const handleDrawerClose = () => { - setOpen(false); - }; - return ( - - - -
{props.children}
-
-)}; -export default Layout; diff --git a/src/apiUtil.js b/src/apiUtil.js deleted file mode 100644 index f2604308..00000000 --- a/src/apiUtil.js +++ /dev/null @@ -1,76 +0,0 @@ -import { API_ERROR_MESSAGE } from "./constants"; -import { getAccessToken } from "./tokenUtility"; - -const getHeaders = () => ({ - Accept: "application/json", - "Content-Type": "application/json", - pragma: "no-cache", - "cache-control": "no-cache", - Authorization: getAccessToken(), -}); - -const getFormHeaders = () => ({ - accept: "application/json", - pragma: "no-cache", - "cache-control": "no-cache", - Authorization: getAccessToken(), -}); - -export const get = (url) => - fetch(url, { - headers: getHeaders(), - method: "GET", - }).then(handleHttpResponse); - -export const post = (url, payload) => - fetch(url, { - headers: getHeaders(), - method: "POST", - body: JSON.stringify(payload), - }).then(handleHttpResponse); - -export const postFormBody = (url, payload) => { - const formData = new FormData(); - for (const [key, value] of Object.entries(payload)) { - formData.append(key, value); - } - return fetch(url, { - method: "POST", - headers: getFormHeaders(), - body: formData, - }).then(handleHttpResponse); -}; - -export const put = (url, payload) => - fetch(url, { - headers: getHeaders(), - method: "PUT", - body: JSON.stringify(payload), - }).then(handleHttpResponse); - -export const putFormBody = (url, payload) => { - const formData = new FormData(); - for (const [key, value] of Object.entries(payload)) { - formData.append(key, value); - } - return fetch(url, { - method: "PUT", - headers: getFormHeaders(), - body: formData, - }).then(handleHttpResponse); -}; - -export const deleteItem = (url, payload) => - fetch(url, { - headers: getHeaders(), - method: "DELETE", - body: JSON.stringify(payload), - }).then(handleHttpResponse); - -const handleHttpResponse = (response) => { - if (response.ok) return response.json(); - if (response.status === 401) { - throw new Error("You are not authorized to access this page."); - } - throw new Error(API_ERROR_MESSAGE); -}; diff --git a/src/app/slices/authSlice.js b/src/app/slices/authSlice.js deleted file mode 100644 index 357c87d2..00000000 --- a/src/app/slices/authSlice.js +++ /dev/null @@ -1,79 +0,0 @@ -import { createSlice } from '@reduxjs/toolkit'; -import { STORAGE_KEY_TOKEN } from '../../constants'; - -// Helper functions -const getUserFromStorage = () => { - try { - const result = window.localStorage.getItem(STORAGE_KEY_TOKEN); - return result ? JSON.parse(result) : null; - } catch (error) { - console.error('Error reading user from storage:', error); - return null; - } -}; - -const isTokenValid = (token) => { - if (!token) return false; - try { - const payload = JSON.parse(atob(token.split('.')[1])); - return new Date(payload.exp * 1000) > new Date(); - } catch (e) { - return false; - } -}; - -const initialState = { - user: getUserFromStorage(), - isAuthenticated: (() => { - const user = getUserFromStorage(); - return user?.token ? isTokenValid(user.token) : false; - })(), - loading: false, - error: null, -}; - -const authSlice = createSlice({ - name: 'auth', - initialState, - reducers: { - setUser: (state, action) => { - state.user = action.payload; - state.isAuthenticated = action.payload?.token ? isTokenValid(action.payload.token) : false; - state.error = null; - - // Persist to localStorage - if (action.payload?.token) { - window.localStorage.setItem(STORAGE_KEY_TOKEN, JSON.stringify(action.payload)); - } else { - window.localStorage.removeItem(STORAGE_KEY_TOKEN); - } - }, - logout: (state) => { - state.user = null; - state.isAuthenticated = false; - state.error = null; - window.localStorage.removeItem(STORAGE_KEY_TOKEN); - }, - setLoading: (state, action) => { - state.loading = action.payload; - }, - setError: (state, action) => { - state.error = action.payload; - state.loading = false; - }, - clearError: (state) => { - state.error = null; - }, - }, -}); - -export const { setUser, logout, setLoading, setError, clearError } = authSlice.actions; - -// Selectors -export const selectUser = (state) => state.auth.user; -export const selectIsAuthenticated = (state) => state.auth.isAuthenticated; -export const selectAuthLoading = (state) => state.auth.loading; -export const selectAuthError = (state) => state.auth.error; -export const selectToken = (state) => state.auth.user?.token; - -export default authSlice.reducer; diff --git a/src/app/slices/uiSlice.js b/src/app/slices/uiSlice.js deleted file mode 100644 index 990208e9..00000000 --- a/src/app/slices/uiSlice.js +++ /dev/null @@ -1,54 +0,0 @@ -import { createSlice } from '@reduxjs/toolkit'; -import { THEME_LIGHT } from '../../constants'; - -const initialState = { - sidebarOpen: true, - theme: THEME_LIGHT, - notifications: [], -}; - -const uiSlice = createSlice({ - name: 'ui', - initialState, - reducers: { - toggleSidebar: (state) => { - state.sidebarOpen = !state.sidebarOpen; - }, - setSidebarOpen: (state, action) => { - state.sidebarOpen = action.payload; - }, - setTheme: (state, action) => { - state.theme = action.payload; - }, - addNotification: (state, action) => { - state.notifications.push({ - id: Date.now(), - ...action.payload, - }); - }, - removeNotification: (state, action) => { - state.notifications = state.notifications.filter( - (notification) => notification.id !== action.payload - ); - }, - clearNotifications: (state) => { - state.notifications = []; - }, - }, -}); - -export const { - toggleSidebar, - setSidebarOpen, - setTheme, - addNotification, - removeNotification, - clearNotifications, -} = uiSlice.actions; - -// Selectors -export const selectSidebarOpen = (state) => state.ui.sidebarOpen; -export const selectTheme = (state) => state.ui.theme; -export const selectNotifications = (state) => state.ui.notifications; - -export default uiSlice.reducer; diff --git a/src/app/store.js b/src/app/store.js deleted file mode 100644 index 0845ea6d..00000000 --- a/src/app/store.js +++ /dev/null @@ -1,19 +0,0 @@ -import { configureStore } from '@reduxjs/toolkit'; -import authReducer from './slices/authSlice'; -import uiReducer from './slices/uiSlice'; - -export const store = configureStore({ - reducer: { - auth: authReducer, - ui: uiReducer, - }, - middleware: (getDefaultMiddleware) => - getDefaultMiddleware({ - serializableCheck: { - // Ignore these action types - ignoredActions: ['auth/setUser'], - }, - }), -}); - -export default store; diff --git a/src/components/ActionBar.css b/src/components/ActionBar.css deleted file mode 100644 index b373120f..00000000 --- a/src/components/ActionBar.css +++ /dev/null @@ -1,122 +0,0 @@ -/* Action Bar - Top navigation bar above search controls */ -.actionBar { - background-color: #f5f5f5; - border-bottom: 1px solid #ddd; - padding: 0; - margin-bottom: 0; - position: relative; - z-index: 10; -} - -.actionBarNav { - list-style: none; - margin: 0; - padding: 0; - display: flex; - align-items: center; - gap: 0; -} - -.actionBarNav > li { - position: relative; - display: inline-block; -} - -.actionBarLink { - display: flex; - align-items: center; - gap: 6px; - padding: 10px 15px; - color: #333; - text-decoration: none; - transition: background-color 0.2s; - white-space: nowrap; -} - -.actionBarLink:hover { - background-color: #e0e0e0; - color: #000; - text-decoration: none; -} - -.actionBarLink i { - font-size: 14px; -} - -.actionBarText { - font-size: 13px; -} - -/* Dropdown */ -.actionBarDropdown { - position: relative; - z-index: 100; -} - -.actionBarDropdownMenu { - position: absolute; - top: 100%; - left: 0; - z-index: 1050; - display: block; - min-width: 180px; - padding: 5px 0; - margin: 0; - list-style: none; - background-color: #fff; - border: 1px solid #ccc; - border-radius: 4px; - box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); -} - -.actionBarDropdownMenu li { - display: block; -} - -.actionBarDropdownMenu li a { - display: block; - padding: 8px 20px; - color: #333; - text-decoration: none; - font-size: 13px; - white-space: nowrap; -} - -.actionBarDropdownMenu li a:hover { - background-color: #f5f5f5; - color: #000; -} - -.actionBarDropdownMenu li.divider { - height: 1px; - margin: 5px 0; - overflow: hidden; - background-color: #e5e5e5; -} - -.countLabel { - color: #999; - font-size: 12px; -} - -.pull-right { - float: right; - margin-left: 10px; -} - -/* Hide text on small screens */ -@media (max-width: 768px) { - .actionBarText { - display: none; - } -} - -/* Close dropdowns when clicking outside */ -.actionBarDropdown.open .actionBarDropdownMenu { - display: block; -} - -.actionBarDropdown:not(.open) .actionBarDropdownMenu { - display: none; -} - diff --git a/src/components/ActionBar.js b/src/components/ActionBar.js deleted file mode 100644 index 92a27d3e..00000000 --- a/src/components/ActionBar.js +++ /dev/null @@ -1,164 +0,0 @@ -import React, { useState } from "react"; -import "./ActionBar.css"; - -export default function ActionBar({ - moduleName = "Items", - moduleIcon = "fa-list", - onNew, - onBulkEdit, - onImportExport, - onSearch, - selectedCount = 0 -}) { - const [workspaceOpen, setWorkspaceOpen] = useState(false); - const [moduleOpen, setModuleOpen] = useState(false); - const [searchOpen, setSearchOpen] = useState(false); - const [selectedWorkspace, setSelectedWorkspace] = useState("All"); - - const handleWorkspaceSelect = (workspace) => { - setSelectedWorkspace(workspace); - setWorkspaceOpen(false); - }; - - const toggleWorkspace = (e) => { - e.preventDefault(); - e.stopPropagation(); - setWorkspaceOpen(!workspaceOpen); - setModuleOpen(false); - setSearchOpen(false); - }; - - const toggleModule = (e) => { - e.preventDefault(); - e.stopPropagation(); - setModuleOpen(!moduleOpen); - setWorkspaceOpen(false); - setSearchOpen(false); - }; - - const toggleSearch = (e) => { - e.preventDefault(); - e.stopPropagation(); - setSearchOpen(!searchOpen); - setWorkspaceOpen(false); - setModuleOpen(false); - }; - - // Close dropdowns when clicking outside - React.useEffect(() => { - const handleClickOutside = () => { - setWorkspaceOpen(false); - setModuleOpen(false); - setSearchOpen(false); - }; - - if (workspaceOpen || moduleOpen || searchOpen) { - document.addEventListener("click", handleClickOutside); - return () => document.removeEventListener("click", handleClickOutside); - } - }, [workspaceOpen, moduleOpen, searchOpen]); - - return ( -
- -
- ); -} - diff --git a/src/components/DeleteModal.js b/src/components/DeleteModal.js deleted file mode 100644 index 8e141e33..00000000 --- a/src/components/DeleteModal.js +++ /dev/null @@ -1,141 +0,0 @@ -import React from "react"; -import { toast } from "react-toastify"; - -export default function DeleteModal({ - isOpen, - onClose, - onConfirm, - title = "Delete Confirmation", - message = "Are you sure you want to delete this item?", - loading = false -}) { - if (!isOpen) return null; - - return ( -
-
-
-

{title}

- -
- -
-

{message}

-
- -
- - -
-
-
- ); -} - -const styles = { - overlay: { - position: "fixed", - top: 0, - left: 0, - right: 0, - bottom: 0, - backgroundColor: "rgba(0, 0, 0, 0.5)", - display: "flex", - alignItems: "center", - justifyContent: "center", - zIndex: 1000, - }, - modal: { - backgroundColor: "#fff", - borderRadius: "8px", - boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)", - maxWidth: "400px", - width: "90%", - overflow: "hidden", - }, - header: { - backgroundColor: "#f8f9fa", - padding: "16px 20px", - borderBottom: "1px solid #ddd", - display: "flex", - justifyContent: "space-between", - alignItems: "center", - }, - title: { - margin: 0, - fontSize: "18px", - fontWeight: 600, - color: "#333", - }, - closeBtn: { - background: "none", - border: "none", - fontSize: "24px", - cursor: "pointer", - color: "#666", - padding: "0", - width: "32px", - height: "32px", - display: "flex", - alignItems: "center", - justifyContent: "center", - }, - body: { - padding: "20px", - }, - message: { - margin: 0, - fontSize: "14px", - color: "#666", - lineHeight: "1.5", - }, - actions: { - display: "flex", - gap: "10px", - padding: "16px 20px", - borderTop: "1px solid #ddd", - backgroundColor: "#f8f9fa", - }, - cancelBtn: { - flex: 1, - padding: "8px 16px", - fontSize: "14px", - fontWeight: 500, - border: "1px solid #ddd", - borderRadius: "4px", - backgroundColor: "#fff", - color: "#333", - cursor: "pointer", - transition: "all 0.2s", - }, - deleteBtn: { - flex: 1, - padding: "8px 16px", - fontSize: "14px", - fontWeight: 500, - border: "none", - borderRadius: "4px", - backgroundColor: "#dc3545", - color: "#fff", - cursor: "pointer", - transition: "all 0.2s", - }, -}; diff --git a/src/components/SharedTable.js b/src/components/SharedTable.js deleted file mode 100644 index 594949f8..00000000 --- a/src/components/SharedTable.js +++ /dev/null @@ -1,284 +0,0 @@ -import React, { useState, useCallback, useRef, useEffect } from "react"; -import ReactDOM from "react-dom"; - -function ColumnConfigPanel({ columns, columnOrder, hiddenColumns, onReorder, onToggle, onClose }) { - const btnRef = useRef(null); - const panelRef = useRef(null); - const [open, setOpen] = useState(false); - const [menuPos, setMenuPos] = useState({ top: 0, right: 0 }); - const [dragIdx, setDragIdx] = useState(null); - - useEffect(() => { - const handleClick = (e) => { - if (btnRef.current?.contains(e.target)) return; - if (panelRef.current?.contains(e.target)) return; - setOpen(false); - }; - document.addEventListener("mousedown", handleClick); - return () => document.removeEventListener("mousedown", handleClick); - }, []); - - const handleOpen = () => { - if (btnRef.current) { - const rect = btnRef.current.getBoundingClientRect(); - setMenuPos({ top: rect.bottom + 4, right: window.innerWidth - rect.right }); - } - setOpen(!open); - }; - - const handleDragStart = (idx) => setDragIdx(idx); - const handleDragOver = (e) => e.preventDefault(); - const handleDrop = (targetIdx) => { - if (dragIdx === null || dragIdx === targetIdx) return; - const newOrder = [...columnOrder]; - const [moved] = newOrder.splice(dragIdx, 1); - newOrder.splice(targetIdx, 0, moved); - onReorder(newOrder); - setDragIdx(null); - }; - - const ordered = columnOrder.map((key) => columns.find((c) => c.key === key)).filter(Boolean); - - return ( - <> - - {open && ReactDOM.createPortal( -
-
Columns
-
- {ordered.map((col, idx) => ( -
handleDragStart(idx)} - onDragOver={handleDragOver} - onDrop={() => handleDrop(idx)} - > - ⠿ - -
- ))} -
-
, - document.body - )} - - ); -} - -const SharedTable = ({ - rows = [], - columns = [], - onEdit, - onDelete, - page, - setPage, - pageSize, - setPageSize, - totalCount, - loading, - error, - sortBy, - setSortBy, - sortDir, - setSortDir -}) => { - const totalPages = Math.max(1, Math.ceil(totalCount / pageSize)); - const [colWidths, setColWidths] = useState(null); - const [columnOrder, setColumnOrder] = useState(() => columns.map((c) => c.key)); - const [hiddenColumns, setHiddenColumns] = useState([]); - const gridRef = useRef(null); - - useEffect(() => { - setColumnOrder((prev) => { - const newKeys = columns.map((c) => c.key); - const existing = prev.filter((k) => newKeys.includes(k)); - const added = newKeys.filter((k) => !prev.includes(k)); - return [...existing, ...added]; - }); - }, [columns]); - - const visibleColumns = columnOrder - .map((key) => columns.find((c) => c.key === key)) - .filter((col) => col && !hiddenColumns.includes(col.key)); - - const handleToggleColumn = (key) => { - setHiddenColumns((prev) => - prev.includes(key) ? prev.filter((k) => k !== key) : [...prev, key] - ); - setColWidths(null); - }; - - const snapshotWidths = useCallback(() => { - if (!gridRef.current) return null; - const headerCells = gridRef.current.querySelectorAll("[data-col]"); - const widths = {}; - headerCells.forEach((cell) => { - widths[cell.dataset.col] = cell.getBoundingClientRect().width; - }); - return widths; - }, []); - - const handleMouseDown = useCallback((e, colKey) => { - e.preventDefault(); - const startX = e.clientX; - - const currentWidths = snapshotWidths(); - if (!currentWidths) return; - setColWidths(currentWidths); - - const colIndex = visibleColumns.findIndex((c) => c.key === colKey); - const nextColKey = colIndex < visibleColumns.length - 1 ? visibleColumns[colIndex + 1].key : null; - const startWidth = currentWidths[colKey] || 100; - const nextStartWidth = nextColKey ? (currentWidths[nextColKey] || 100) : 0; - - const onMove = (moveEvent) => { - let diff = moveEvent.clientX - startX; - const minCol = 50; - if (startWidth + diff < minCol) diff = minCol - startWidth; - if (nextColKey && nextStartWidth - diff < minCol) diff = nextStartWidth - minCol; - setColWidths((prev) => { - const updated = { ...prev, [colKey]: startWidth + diff }; - if (nextColKey) updated[nextColKey] = nextStartWidth - diff; - return updated; - }); - }; - - const onUp = () => { - document.removeEventListener("mousemove", onMove); - document.removeEventListener("mouseup", onUp); - document.body.style.cursor = ""; - document.body.style.userSelect = ""; - }; - - document.body.style.cursor = "col-resize"; - document.body.style.userSelect = "none"; - document.addEventListener("mousemove", onMove); - document.addEventListener("mouseup", onUp); - }, [snapshotWidths, visibleColumns]); - - const handleSort = (key) => { - if (!setSortBy || !setSortDir) return; - if (sortBy === key) { - setSortDir(sortDir === "asc" ? "desc" : "asc"); - } else { - setSortBy(key); - setSortDir("asc"); - } - if (setPage) setPage(1); - }; - - if (loading) return
Loading...
; - if (error) return
{error}
; - - const gridTemplate = colWidths - ? ["40px", ...visibleColumns.map((col) => `${colWidths[col.key] || 100}px`), "80px"].join(" ") - : ["40px", ...visibleColumns.map(() => "1fr"), "80px"].join(" "); - - const paginationBar = ( -
-
- Show -
- {[12, 24, 48, 96].map((size) => ( - - ))} -
- per page -
-
- - {Math.min((page - 1) * pageSize + 1, totalCount)}–{Math.min(page * pageSize, totalCount)} of {totalCount} - -
- - - {page} - - -
- { setColumnOrder(order); setColWidths(null); }} - onToggle={handleToggleColumn} - /> -
-
- ); - - return ( -
- {paginationBar} -
-
-
- -
- {visibleColumns.map((col) => ( -
handleSort(col.key)} - > - {col.label} - {sortBy === col.key && {sortDir === "asc" ? " ▲" : " ▼"}} - { e.stopPropagation(); handleMouseDown(e, col.key); }} - /> -
- ))} -
Actions
- - {rows.map((row, idx) => ( - -
- -
- {visibleColumns.map((col) => ( -
- {col.render ? col.render(row[col.key], row) : row[col.key]} -
- ))} -
-
- {onEdit && ( - - )} - {onDelete && ( - - )} -
-
-
- ))} -
-
-
- ); -}; - -export default SharedTable; diff --git a/src/components/Sidebar.js b/src/components/Sidebar.js deleted file mode 100644 index 0225b93a..00000000 --- a/src/components/Sidebar.js +++ /dev/null @@ -1,111 +0,0 @@ -import React from "react"; -import { NavLink } from "react-router-dom"; -import { - FaTachometerAlt, - FaCalendarAlt, - FaFileAlt, - FaUserTie, - FaMoneyCheckAlt, - FaAddressBook, - FaChartBar, - FaTools, - FaBell, - FaClipboardList, - FaCubes, - FaMapMarkerAlt, - FaCog, - FaTruck, - FaDollarSign, - FaFileInvoiceDollar, -} from "react-icons/fa"; - -const navGroups = [ - { - title: "Overview", - items: [ - { label: "Dashboard", icon: , path: "/" }, - { label: "Calendar", icon: , path: "/calendar" }, - { label: "Reports", icon: , path: "/reports" }, - ], - }, - { - title: "Operations", - items: [ - { label: "Work Orders", icon: , path: "/workorders" }, - { label: "Uplifts", icon: , path: "/uplifts" }, - { label: "Follow Ups", icon: , path: "/followups" }, - { label: "PM Schedules", icon: , path: "/pmschedules" }, - ], - }, - { - title: "Vendors", - items: [ - { label: "Vendors", icon: , path: "/vendors" }, - { label: "Vendor POs", icon: , path: "/vendor-pos" }, - ], - }, - { - title: "Customers & Sites", - items: [ - { label: "Accounts", icon: , path: "/accounts" }, - { label: "Contacts", icon: , path: "/contacts" }, - { label: "Locations", icon: , path: "/locations" }, - { label: "Assets", icon: , path: "/assets" }, - ], - }, - { - title: "Internal", - items: [ - { label: "Employees", icon: , path: "/employees" }, - { label: "Documents", icon: , path: "/documents" }, - ], - }, - { - title: "System", - items: [ - { label: "Settings", icon: , path: "/settings/dropdowns" }, - ], - }, -]; - -export default function Sidebar() { - return ( - - ); -} diff --git a/src/components/SignatureCapture.css b/src/components/SignatureCapture.css deleted file mode 100644 index 15f281f2..00000000 --- a/src/components/SignatureCapture.css +++ /dev/null @@ -1,125 +0,0 @@ -.sigOverlay { - position: fixed; - inset: 0; - background: rgba(0,0,0,0.4); - z-index: 100000; - display: flex; - align-items: center; - justify-content: center; -} - -.sigModal { - background: #fff; - border-radius: 8px; - width: 520px; - box-shadow: 0 12px 40px rgba(0,0,0,0.2); -} - -.sigHeader { - display: flex; - justify-content: space-between; - align-items: center; - padding: 14px 20px; - border-bottom: 1px solid #eee; -} - -.sigHeader h3 { - margin: 0; - font-size: 16px; - font-weight: 600; -} - -.sigBody { - padding: 20px; -} - -.sigModeTabs { - display: flex; - gap: 0; - margin-bottom: 16px; - border-bottom: 2px solid #e5e5e5; -} - -.sigNameField { - margin-bottom: 12px; -} - -.sigNameField label { - display: block; - font-size: 12px; - font-weight: 600; - color: #666; - margin-bottom: 4px; -} - -.sigModeTab { - padding: 6px 16px; - border: none; - background: none; - font-size: 13px; - font-weight: 600; - color: #999; - cursor: pointer; - border-bottom: 2px solid transparent; - margin-bottom: -2px; -} - -.sigModeTab.active { - color: #007bff; - border-bottom-color: #007bff; -} - -.sigCanvasWrap { - position: relative; -} - -.sigCanvas { - border: 1px solid #ddd; - border-radius: 4px; - cursor: crosshair; - display: block; - width: 100%; - touch-action: none; -} - -.sigClearBtn { - position: absolute; - top: 8px; - right: 8px; - padding: 2px 10px; - font-size: 11px; - border: 1px solid #ddd; - background: #fff; - border-radius: 3px; - cursor: pointer; - color: #666; -} - -.sigTypeWrap { - display: flex; - flex-direction: column; - gap: 12px; -} - -.sigTypeInput { - font-size: 16px; -} - -.sigTypePreview { - padding: 20px; - text-align: center; - font-family: "Brush Script MT", "Segoe Script", cursive; - font-size: 36px; - color: #333; - border: 1px solid #eee; - border-radius: 4px; - background: #fafafa; -} - -.sigFooter { - display: flex; - justify-content: flex-end; - gap: 8px; - padding: 14px 20px; - border-top: 1px solid #eee; -} diff --git a/src/components/SignatureCapture.js b/src/components/SignatureCapture.js deleted file mode 100644 index dc9e5ac0..00000000 --- a/src/components/SignatureCapture.js +++ /dev/null @@ -1,123 +0,0 @@ -import React, { useState, useRef, useEffect } from "react"; -import ReactDOM from "react-dom"; - -export default function SignatureCapture({ onSave, onClose, title }) { - const [mode, setMode] = useState("draw"); - const [typedName, setTypedName] = useState(""); - const [signerName, setSignerName] = useState(""); - const canvasRef = useRef(null); - const isDrawing = useRef(false); - - useEffect(() => { - if (mode === "draw" && canvasRef.current) { - const canvas = canvasRef.current; - const ctx = canvas.getContext("2d"); - ctx.fillStyle = "#fff"; - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.strokeStyle = "#333"; - ctx.lineWidth = 2; - ctx.lineCap = "round"; - } - }, [mode]); - - const getPos = (e) => { - const rect = canvasRef.current.getBoundingClientRect(); - const touch = e.touches ? e.touches[0] : e; - return { x: touch.clientX - rect.left, y: touch.clientY - rect.top }; - }; - - const startDraw = (e) => { - e.preventDefault(); - isDrawing.current = true; - const ctx = canvasRef.current.getContext("2d"); - const pos = getPos(e); - ctx.beginPath(); - ctx.moveTo(pos.x, pos.y); - }; - - const draw = (e) => { - e.preventDefault(); - if (!isDrawing.current) return; - const ctx = canvasRef.current.getContext("2d"); - const pos = getPos(e); - ctx.lineTo(pos.x, pos.y); - ctx.stroke(); - }; - - const stopDraw = () => { isDrawing.current = false; }; - - const clearCanvas = () => { - const ctx = canvasRef.current.getContext("2d"); - ctx.fillStyle = "#fff"; - ctx.fillRect(0, 0, canvasRef.current.width, canvasRef.current.height); - }; - - const handleSave = () => { - if (!signerName.trim()) return; - if (mode === "draw") { - const dataUrl = canvasRef.current.toDataURL("image/png"); - onSave(dataUrl, "drawn", signerName.trim()); - } else { - onSave(typedName, "typed", signerName.trim()); - } - }; - - return ReactDOM.createPortal( -
-
e.stopPropagation()}> -
-

{title || "Capture Signature"}

- -
-
-
- - setSignerName(e.target.value)} - placeholder="Enter signer's full name" - /> -
-
- - -
- {mode === "draw" ? ( -
- - -
- ) : ( -
- setTypedName(e.target.value)} - placeholder="Type your name" - /> - {typedName &&
{typedName}
} -
- )} -
-
- - -
-
-
, - document.body - ); -} diff --git a/src/components/Topbar.js b/src/components/Topbar.js deleted file mode 100644 index 7fa3fbaf..00000000 --- a/src/components/Topbar.js +++ /dev/null @@ -1,142 +0,0 @@ -import React, { useState, useEffect, useRef } from "react"; -import ReactDOM from "react-dom"; -import { useNavigate } from "react-router-dom"; -import { useAuth } from "../hooks/useAuth"; - -function NotificationDropdown() { - const [open, setOpen] = useState(false); - const btnRef = useRef(null); - const menuRef = useRef(null); - const [menuPos, setMenuPos] = useState({ top: 0, right: 0 }); - - useEffect(() => { - const handleClick = (e) => { - if (btnRef.current?.contains(e.target)) return; - if (menuRef.current?.contains(e.target)) return; - setOpen(false); - }; - document.addEventListener("mousedown", handleClick); - return () => document.removeEventListener("mousedown", handleClick); - }, []); - - const handleOpen = () => { - if (btnRef.current) { - const rect = btnRef.current.getBoundingClientRect(); - setMenuPos({ top: rect.bottom + 8, right: window.innerWidth - rect.right }); - } - setOpen(!open); - }; - - return ( - <> - - {open && ReactDOM.createPortal( -
-
Notifications
-
- - No new notifications -
-
, - document.body - )} - - ); -} - -function UserDropdown({ currentUser, onLogout, onNavigate }) { - const [open, setOpen] = useState(false); - const btnRef = useRef(null); - const menuRef = useRef(null); - const [menuPos, setMenuPos] = useState({ top: 0, right: 0 }); - - useEffect(() => { - const handleClick = (e) => { - if (btnRef.current?.contains(e.target)) return; - if (menuRef.current?.contains(e.target)) return; - setOpen(false); - }; - document.addEventListener("mousedown", handleClick); - return () => document.removeEventListener("mousedown", handleClick); - }, []); - - const handleOpen = () => { - if (btnRef.current) { - const rect = btnRef.current.getBoundingClientRect(); - setMenuPos({ top: rect.bottom + 8, right: window.innerWidth - rect.right }); - } - setOpen(!open); - }; - - const handleItemClick = (action) => { - setOpen(false); - action(); - }; - - return ( - <> - - {open && ReactDOM.createPortal( -
-
- -
-
{currentUser?.name || "User"}
-
{currentUser?.email || ""}
-
-
-
- - -
, - document.body - )} - - ); -} - -export default function Topbar({ onToggleNav }) { - const navigate = useNavigate(); - const { user, logout: performLogout } = useAuth(); - - const handleLogout = () => { - performLogout(); // This now uses Redux and automatically navigates - }; - - return ( -
-
- -
- -
- -
- -
-
- ); -} diff --git a/src/constants/actionTypes.js b/src/constants/actionTypes.js deleted file mode 100644 index fed9bf21..00000000 --- a/src/constants/actionTypes.js +++ /dev/null @@ -1,16 +0,0 @@ -// Redux Action Types - -// Auth Actions -export const AUTH_SET_USER = 'auth/setUser'; -export const AUTH_LOGOUT = 'auth/logout'; -export const AUTH_SET_LOADING = 'auth/setLoading'; -export const AUTH_SET_ERROR = 'auth/setError'; -export const AUTH_CLEAR_ERROR = 'auth/clearError'; - -// UI Actions -export const UI_TOGGLE_SIDEBAR = 'ui/toggleSidebar'; -export const UI_SET_SIDEBAR_OPEN = 'ui/setSidebarOpen'; -export const UI_SET_THEME = 'ui/setTheme'; -export const UI_ADD_NOTIFICATION = 'ui/addNotification'; -export const UI_REMOVE_NOTIFICATION = 'ui/removeNotification'; -export const UI_CLEAR_NOTIFICATIONS = 'ui/clearNotifications'; diff --git a/src/constants/index.js b/src/constants/index.js deleted file mode 100644 index b44ac009..00000000 --- a/src/constants/index.js +++ /dev/null @@ -1,87 +0,0 @@ -// Main constants file - exports all constants - -// API Constants -export const ADD = "add"; -export const EDIT = "edit"; - -export const API_ERROR_MESSAGE = "Sorry, something went wrong. Please try again later."; -export const API_SUCCESS_MESSAGE = "Your changes have been successfully saved."; -export const API_DELETE_SUCCESS_MESSAGE = "Item deleted successfully"; - -export const API_URL = process.env.REACT_APP_API_URL || "/api"; - -// Cache Times (in milliseconds) -export const CACHE_TIME_SHORT = 2 * 60 * 1000; // 2 minutes -export const CACHE_TIME_MEDIUM = 5 * 60 * 1000; // 5 minutes -export const CACHE_TIME_LONG = 10 * 60 * 1000; // 10 minutes -export const CACHE_TIME_VERY_LONG = 30 * 60 * 1000; // 30 minutes - -// Stale Times (in milliseconds) -export const STALE_TIME_SHORT = 1 * 60 * 1000; // 1 minute -export const STALE_TIME_MEDIUM = 5 * 60 * 1000; // 5 minutes -export const STALE_TIME_LONG = 10 * 60 * 1000; // 10 minutes -export const STALE_TIME_VERY_LONG = 30 * 60 * 1000; // 30 minutes - -// Pagination -export const DEFAULT_PAGE_SIZE = 12; -export const DEFAULT_PAGE = 1; - -// Debounce Times -export const DEBOUNCE_SEARCH = 600; // 600ms for search inputs -export const DEBOUNCE_RESIZE = 300; // 300ms for window resize -export const DEBOUNCE_INPUT = 400; // 400ms for general inputs - -// Local Storage Keys -export const STORAGE_KEY_TOKEN = 'token'; -export const STORAGE_KEY_THEME = 'theme'; -export const STORAGE_KEY_SIDEBAR = 'sidebarOpen'; - -// Themes -export const THEME_LIGHT = 'light'; -export const THEME_DARK = 'dark'; - -// View Modes -export const VIEW_MODE_LIST = 'list'; -export const VIEW_MODE_CARD = 'card'; -export const VIEW_MODE_GRID = 'grid'; - -// Status Types -export const STATUS_OPEN = 'Open'; -export const STATUS_CLOSED = 'Closed'; -export const STATUS_PENDING = 'Pending'; -export const STATUS_COMPLETED = 'Completed'; -export const STATUS_CANCELLED = 'Cancelled'; -export const STATUS_IN_PROGRESS = 'In Progress'; - -// Priority Levels -export const PRIORITY_LOW = 'Low'; -export const PRIORITY_MEDIUM = 'Medium'; -export const PRIORITY_HIGH = 'High'; -export const PRIORITY_URGENT = 'Urgent'; - -// Form Modes -export const FORM_MODE_CREATE = 'create'; -export const FORM_MODE_EDIT = 'edit'; -export const FORM_MODE_VIEW = 'view'; - -// Helper function for form text -export const form_text_builder = (mode) => { - let PRIMARY_TEXT = ""; - let SECONDARY_TEXT = ""; - let BUTTON_TEXT = ""; - - if (mode === ADD) { - PRIMARY_TEXT = "Add an Item"; - SECONDARY_TEXT = "Add your new item below"; - BUTTON_TEXT = "Add Item"; - } else { - PRIMARY_TEXT = "Edit an Item"; - SECONDARY_TEXT = "Edit your item below"; - BUTTON_TEXT = "Save Item"; - } - return { PRIMARY_TEXT, SECONDARY_TEXT, BUTTON_TEXT }; -}; - -// Re-export all action types and query keys -export * from './actionTypes'; -export * from './queryKeys'; diff --git a/src/constants/queryKeys.js b/src/constants/queryKeys.js deleted file mode 100644 index 9619efb2..00000000 --- a/src/constants/queryKeys.js +++ /dev/null @@ -1,82 +0,0 @@ -// React Query Keys - Centralized query key management - -// Work Orders -export const WORK_ORDERS_LIST = 'workOrdersList'; -export const WORK_ORDERS_DETAIL = 'workOrdersDetail'; -export const WORK_ORDERS_BY_LOCATION = 'workOrdersByLocation'; -export const WORK_ORDERS_BY_ACCOUNT = 'workOrdersByAccount'; - -// PM Schedules -export const PM_SCHEDULES_LIST = 'pmSchedulesList'; -export const PM_SCHEDULES_DETAIL = 'pmSchedulesDetail'; -export const PM_SCHEDULES_BY_ASSET = 'pmSchedulesByAsset'; -export const PM_SCHEDULES_BY_LOCATION = 'pmSchedulesByLocation'; - -// Follow Ups -export const FOLLOW_UPS_LIST = 'followUpsList'; -export const FOLLOW_UPS_DETAIL = 'followUpsDetail'; -export const FOLLOW_UPS_BY_STATUS = 'followUpsByStatus'; -export const FOLLOW_UPS_BY_WORK_ORDER = 'followUpsByWorkOrder'; - -// Employees -export const EMPLOYEES_LIST = 'employeesList'; -export const EMPLOYEES_DETAIL = 'employeesDetail'; -export const EMPLOYEES_DROPDOWN = 'employeesDropdown'; -export const EMPLOYEES_BY_DEPARTMENT = 'employeesByDepartment'; - -// Contacts -export const CONTACTS_LIST = 'contactsList'; -export const CONTACTS_DETAIL = 'contactsDetail'; -export const CONTACTS_DROPDOWN = 'contactsDropdown'; -export const CONTACTS_BY_LOCATION = 'contactsByLocation'; -export const CONTACTS_BY_ACCOUNT = 'contactsByAccount'; - -// Locations -export const LOCATIONS_LIST = 'locationsList'; -export const LOCATIONS_DETAIL = 'locationsDetail'; -export const LOCATIONS_DROPDOWN = 'locationsDropdown'; -export const LOCATIONS_BY_ACCOUNT = 'locationsByAccount'; - -// Accounts -export const ACCOUNTS_LIST = 'accountsList'; -export const ACCOUNTS_DETAIL = 'accountsDetail'; -export const ACCOUNTS_DROPDOWN = 'accountsDropdown'; - -// Assets -export const ASSETS_LIST = 'assetsList'; -export const ASSETS_DETAIL = 'assetsDetail'; -export const ASSETS_BY_LOCATION = 'assetsByLocation'; -export const ASSETS_BY_ACCOUNT = 'assetsByAccount'; - -// Vendors -export const VENDORS_LIST = 'vendorsList'; -export const VENDORS_DETAIL = 'vendorsDetail'; -export const VENDORS_DROPDOWN = 'vendorsDropdown'; -export const VENDORS_BY_TRADE = 'vendorsByTrade'; - -// Dashboard -export const DASHBOARD_STATS = 'dashboardStats'; -export const DASHBOARD_RECENT_WORK_ORDERS = 'dashboardRecentWorkOrders'; -export const DASHBOARD_OVERDUE_WORK_ORDERS = 'dashboardOverdueWorkOrders'; - -// Dropdowns -export const DROPDOWN_OPTIONS_BY_CATEGORY = 'dropdownOptionsByCategory'; -export const DROPDOWN_PARENTS = 'dropdownParents'; - -// Dispatches -export const DISPATCHES_LIST = 'dispatchesList'; -export const DISPATCHES_DETAIL = 'dispatchesDetail'; -export const DISPATCHES_BY_VENDOR = 'dispatchesByVendor'; - -// Task Templates -export const TASK_TEMPLATES_LIST = 'taskTemplatesList'; -export const TASK_TEMPLATES_DETAIL = 'taskTemplatesDetail'; - -// Uplifts -export const UPLIFTS_LIST = 'upliftsList'; -export const UPLIFTS_BY_DISPATCH = 'upliftsByDispatch'; - -// Calendar Events -export const CALENDAR_EVENTS_LIST = 'calendarEventsList'; -export const CALENDAR_EVENTS_DETAIL = 'calendarEventsDetail'; -export const CALENDAR_EVENTS_BY_DATE_RANGE = 'calendarEventsByDateRange'; diff --git a/src/hooks/api/useEmployees.js b/src/hooks/api/useEmployees.js deleted file mode 100644 index 86f8942a..00000000 --- a/src/hooks/api/useEmployees.js +++ /dev/null @@ -1,85 +0,0 @@ -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; -import { employeesApi } from '../../lib/api/services'; -import { toast } from 'react-toastify'; -import { - EMPLOYEES_LIST, - EMPLOYEES_DETAIL, - EMPLOYEES_DROPDOWN, - API_ERROR_MESSAGE, - STALE_TIME_LONG -} from '../../constants'; - -export function useEmployees(params = {}) { - return useQuery({ - queryKey: [EMPLOYEES_LIST, params], - queryFn: () => employeesApi.getList(params), - keepPreviousData: true, - }); -} - -export function useEmployee(id, options = {}) { - return useQuery({ - queryKey: [EMPLOYEES_DETAIL, id], - queryFn: () => employeesApi.getById(id), - enabled: !!id, - ...options, - }); -} - -export function useEmployeesDropdown() { - return useQuery({ - queryKey: [EMPLOYEES_DROPDOWN], - queryFn: () => employeesApi.getDropdown(), - staleTime: STALE_TIME_LONG, - }); -} - -export function useCreateEmployee() { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: (data) => employeesApi.create(data), - onSuccess: () => { - queryClient.invalidateQueries([EMPLOYEES_LIST]); - queryClient.invalidateQueries([EMPLOYEES_DROPDOWN]); - toast.success('Employee created successfully!'); - }, - onError: (error) => { - toast.error(error.message || API_ERROR_MESSAGE); - }, - }); -} - -export function useUpdateEmployee() { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: (data) => employeesApi.update(data), - onSuccess: () => { - queryClient.invalidateQueries([EMPLOYEES_LIST]); - queryClient.invalidateQueries([EMPLOYEES_DETAIL]); - queryClient.invalidateQueries([EMPLOYEES_DROPDOWN]); - toast.success('Employee updated successfully!'); - }, - onError: (error) => { - toast.error(error.message || API_ERROR_MESSAGE); - }, - }); -} - -export function useDeleteEmployee() { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: (id) => employeesApi.delete(id), - onSuccess: () => { - queryClient.invalidateQueries([EMPLOYEES_LIST]); - queryClient.invalidateQueries([EMPLOYEES_DETAIL]); - queryClient.invalidateQueries([EMPLOYEES_DROPDOWN]); - toast.success('Employee deleted successfully!'); - }, - onError: (error) => { - toast.error(error.message || API_ERROR_MESSAGE); - }, - }); -} diff --git a/src/hooks/api/usePMSchedules.js b/src/hooks/api/usePMSchedules.js deleted file mode 100644 index 98423bdc..00000000 --- a/src/hooks/api/usePMSchedules.js +++ /dev/null @@ -1,69 +0,0 @@ -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; -import { pmSchedulesApi } from '../../lib/api/services'; -import { toast } from 'react-toastify'; -import { - PM_SCHEDULES_LIST, - PM_SCHEDULES_DETAIL, - API_SUCCESS_MESSAGE, - API_ERROR_MESSAGE, -} from '../../constants'; - -/** - * Hook to fetch PM schedules list - */ -export function usePMSchedules(params = {}) { - return useQuery({ - queryKey: [PM_SCHEDULES_LIST, params], - queryFn: () => pmSchedulesApi.getList(params), - keepPreviousData: true, - }); -} - -/** - * Hook to fetch a single PM schedule by ID - */ -export function usePMSchedule(id, options = {}) { - return useQuery({ - queryKey: [PM_SCHEDULES_DETAIL, id], - queryFn: () => pmSchedulesApi.getById(id), - enabled: !!id, - ...options, - }); -} - -/** - * Hook to save (create/update) a PM schedule - */ -export function useSavePMSchedule() { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: (data) => pmSchedulesApi.save(data), - onSuccess: () => { - queryClient.invalidateQueries([PM_SCHEDULES_LIST]); - queryClient.invalidateQueries([PM_SCHEDULES_DETAIL]); - toast.success('PM schedule saved successfully!'); - }, - onError: (error) => { - toast.error(error.message || API_ERROR_MESSAGE); - }, - }); -} - -/** - * Hook to delete a PM schedule - */ -export function useDeletePMSchedule() { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: (id) => pmSchedulesApi.delete(id), - onSuccess: () => { - queryClient.invalidateQueries([PM_SCHEDULES_LIST]); - toast.success('PM schedule deleted successfully!'); - }, - onError: (error) => { - toast.error(error.message || API_ERROR_MESSAGE); - }, - }); -} diff --git a/src/hooks/api/useWorkOrders.js b/src/hooks/api/useWorkOrders.js deleted file mode 100644 index 49f3d8b0..00000000 --- a/src/hooks/api/useWorkOrders.js +++ /dev/null @@ -1,91 +0,0 @@ -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; -import { workOrdersApi } from '../../lib/api/services'; -import { toast } from 'react-toastify'; -import { - WORK_ORDERS_LIST, - WORK_ORDERS_DETAIL, - API_SUCCESS_MESSAGE, - API_ERROR_MESSAGE -} from '../../constants'; - -/** - * Hook to fetch work orders list with pagination and filters - */ -export function useWorkOrders(params = {}) { - return useQuery({ - queryKey: [WORK_ORDERS_LIST, params], - queryFn: () => workOrdersApi.getList(params), - keepPreviousData: true, - }); -} - -/** - * Hook to fetch a single work order by ID - */ -export function useWorkOrder(id, options = {}) { - return useQuery({ - queryKey: [WORK_ORDERS_DETAIL, id], - queryFn: () => workOrdersApi.getById(id), - enabled: !!id, - ...options, - }); -} - -/** - * Hook to create a new work order - */ -export function useCreateWorkOrder() { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: (data) => workOrdersApi.create(data), - onSuccess: () => { - queryClient.invalidateQueries([WORK_ORDERS_LIST]); - queryClient.invalidateQueries([WORK_ORDERS_DETAIL]); - toast.success('Work order created successfully!'); - }, - onError: (error) => { - toast.error(error.message || API_ERROR_MESSAGE); - }, - }); -} - -/** - * Hook to update a work order - */ -export function useUpdateWorkOrder() { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: (data) => workOrdersApi.update(data), - onSuccess: (data, variables) => { - queryClient.invalidateQueries([WORK_ORDERS_LIST]); - if (variables.id) { - queryClient.invalidateQueries([WORK_ORDERS_DETAIL, variables.id]); - } - toast.success('Work order updated successfully!'); - }, - onError: (error) => { - toast.error(error.message || API_ERROR_MESSAGE); - }, - }); -} - -/** - * Hook to delete a work order - */ -export function useDeleteWorkOrder() { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: (id) => workOrdersApi.delete(id), - onSuccess: () => { - queryClient.invalidateQueries([WORK_ORDERS_LIST]); - queryClient.invalidateQueries([WORK_ORDERS_DETAIL]); - toast.success('Work order deleted successfully!'); - }, - onError: (error) => { - toast.error(error.message || API_ERROR_MESSAGE); - }, - }); -} diff --git a/src/hooks/useAuth.js b/src/hooks/useAuth.js deleted file mode 100644 index 26a6a35e..00000000 --- a/src/hooks/useAuth.js +++ /dev/null @@ -1,75 +0,0 @@ -import { useSelector, useDispatch } from 'react-redux'; -import { useNavigate } from 'react-router-dom'; -import { useMutation } from '@tanstack/react-query'; -import { - selectUser, - selectIsAuthenticated, - selectAuthLoading, - selectAuthError, - selectToken, - setUser, - logout as logoutAction, - setLoading, - setError, -} from '../app/slices/authSlice'; -import { authApi } from '../lib/api/services'; -import { toast } from 'react-toastify'; - -/** - * Custom hook for authentication - * Provides auth state and methods - */ -export function useAuth() { - const dispatch = useDispatch(); - const navigate = useNavigate(); - - const user = useSelector(selectUser); - const isAuthenticated = useSelector(selectIsAuthenticated); - const loading = useSelector(selectAuthLoading); - const error = useSelector(selectAuthError); - const token = useSelector(selectToken); - - const loginMutation = useMutation({ - mutationFn: (credentials) => authApi.login(credentials), - onMutate: () => { - dispatch(setLoading(true)); - }, - onSuccess: (data) => { - dispatch(setUser(data)); - toast.success('Login successful!'); - navigate('/'); - }, - onError: (error) => { - dispatch(setError(error.message || 'Login failed')); - toast.error(error.message || 'Login failed'); - }, - }); - - const logout = () => { - dispatch(logoutAction()); - toast.info('You have been logged out'); - navigate('/login'); - }; - - const changePasswordMutation = useMutation({ - mutationFn: (data) => authApi.changePassword(data), - onSuccess: () => { - toast.success('Password changed successfully!'); - }, - onError: (error) => { - toast.error(error.message || 'Failed to change password'); - }, - }); - - return { - user, - isAuthenticated, - loading: loading || loginMutation.isLoading, - error, - token, - login: loginMutation.mutate, - logout, - changePassword: changePasswordMutation.mutate, - isChangingPassword: changePasswordMutation.isLoading, - }; -} diff --git a/src/hooks/useDebounce.js b/src/hooks/useDebounce.js deleted file mode 100644 index 72b2f037..00000000 --- a/src/hooks/useDebounce.js +++ /dev/null @@ -1,22 +0,0 @@ -import { useState, useEffect } from 'react'; - -/** - * Debounce a value - * @param {*} value - The value to debounce - * @param {number} delay - Delay in milliseconds - */ -export function useDebounce(value, delay = 500) { - const [debouncedValue, setDebouncedValue] = useState(value); - - useEffect(() => { - const handler = setTimeout(() => { - setDebouncedValue(value); - }, delay); - - return () => { - clearTimeout(handler); - }; - }, [value, delay]); - - return debouncedValue; -} diff --git a/src/hooks/useModal.js b/src/hooks/useModal.js deleted file mode 100644 index fe916a8b..00000000 --- a/src/hooks/useModal.js +++ /dev/null @@ -1,33 +0,0 @@ -import { useState, useCallback } from 'react'; - -/** - * Reusable hook for modal state management - */ -export function useModal(initialState = false) { - const [isOpen, setIsOpen] = useState(initialState); - const [data, setData] = useState(null); - - const open = useCallback((modalData = null) => { - setData(modalData); - setIsOpen(true); - }, []); - - const close = useCallback(() => { - setIsOpen(false); - // Delay clearing data to allow for exit animations - setTimeout(() => setData(null), 300); - }, []); - - const toggle = useCallback(() => { - setIsOpen((prev) => !prev); - }, []); - - return { - isOpen, - data, - open, - close, - toggle, - setData, - }; -} diff --git a/src/hooks/usePaginatedList.js b/src/hooks/usePaginatedList.js deleted file mode 100644 index 0ac4f18f..00000000 --- a/src/hooks/usePaginatedList.js +++ /dev/null @@ -1,45 +0,0 @@ -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; - -export default function usePaginatedList(fetchFn, extraParams = {}) { - const [rows, setRows] = useState([]); - const [search, setSearch] = useState(""); - const [debouncedSearch, setDebouncedSearch] = useState(""); - const [page, setPage] = useState(1); - const [pageSize, setPageSize] = useState(12); - const [totalCount, setTotalCount] = useState(0); - const [loading, setLoading] = useState(true); - const [err, setErr] = useState(""); - const [sortBy, setSortBy] = useState("lastUpdated"); - const [sortDir, setSortDir] = useState("desc"); - const debounceRef = useRef(null); - - const extraKey = JSON.stringify(extraParams); - - useEffect(() => { - if (debounceRef.current) clearTimeout(debounceRef.current); - debounceRef.current = setTimeout(() => { - setDebouncedSearch(search); - setPage(1); - }, 600); - return () => clearTimeout(debounceRef.current); - }, [search]); - - const reload = useCallback(() => { - setLoading(true); - setErr(""); - fetchFn({ search: debouncedSearch, page, pageSize, sortBy, sortDir, ...extraParams }) - .then(({ rows, totalCount }) => { - setRows(rows); - setTotalCount(totalCount); - }) - .catch((e) => setErr(e.message || "Failed to load")) - .finally(() => setLoading(false)); - }, [fetchFn, debouncedSearch, page, pageSize, sortBy, sortDir, extraKey]); - - useEffect(() => { reload(); }, [reload]); - - return useMemo(() => ({ - rows, search, setSearch, page, setPage, pageSize, setPageSize, - totalCount, loading, err, reload, sortBy, setSortBy, sortDir, setSortDir - }), [rows, search, page, pageSize, totalCount, loading, err, reload, sortBy, sortDir]); -} diff --git a/src/index.css b/src/index.css deleted file mode 100644 index 945f4f27..00000000 --- a/src/index.css +++ /dev/null @@ -1,569 +0,0 @@ -/* ========================================================= - ROOT + RESET - ========================================================= */ -:root { - --sidebar-w: 220px; - --sidebar-collapsed-w: 56px; - --topbar-h: 64px; - - --blue-main: #0c4f6f; - --blue-dark: #083a52; - --border: #d9dde0; - --bg: #f4f6f7; -} - -* { - box-sizing: border-box; -} - -html, body, #root { - height: 100%; -} - -body { - margin: 0; - font-family: "Segoe UI", Roboto, Helvetica, Arial, sans-serif; - font-size: 13px; - background: var(--bg); - color: #333; -} - -/* ========================================================= - APP LAYOUT - ========================================================= */ -.app { - display: flex; - min-height: 100vh; - width: 100%; -} - -/* ========================================================= - SIDEBAR - ========================================================= */ -.sidebar { - width: var(--sidebar-w); - height: 100vh; - background: #fbfbfb; - border-right: 1px solid var(--border); - padding: 20px; - display: flex; - flex-direction: column; - flex-shrink: 0; - transition: width .25s ease; -} - -/* Collapsed width */ -.app.nav-collapsed .sidebar { - width: var(--sidebar-collapsed-w); -} -/* Sidebar logo */ -.sidebarLogo { - max-width: 140px; - height: auto; - display: block; -} - -/* Center logo when collapsed */ -.app.nav-collapsed .sidebarLogo { - max-width: 32px; - margin: 0 auto; -} - -/* ========================================================= - LOGO - ========================================================= */ -.logoRow { - display: flex; - align-items: center; - gap: 10px; - margin-bottom: 28px; -} - -.logoMark { - font-size: 26px; - font-weight: bold; - color: #e74c3c; -} - -.logoText { - font-size: 17px; - line-height: 1.2; -} - -.logoText span { - font-size: 13px; - color: #666; -} - -/* ========================================================= - NAVIGATION - ========================================================= */ -.navTitle { - font-size: 12px; - font-weight: bold; - letter-spacing: 1.5px; - color: #888; - margin-bottom: 14px; -} - -.navGroup { - list-style: none; -} - -.navGroupList { - list-style: none; - padding: 0; - margin: 0; -} - -.navGroupTitle { - font-size: 11px; - font-weight: 600; - letter-spacing: 1.2px; - text-transform: uppercase; - color: #9aa0a6; - margin-top: 18px; - margin-bottom: 6px; - padding: 0 14px; -} - -.navGroupTitleFirst { - margin-top: 0; -} - -.navList { - list-style: none; - padding: 0; - margin: 0; - flex-grow: 1; -} - -/* Reset anchor defaults */ -.navList a, -.navList a:visited, -.navList a:hover, -.navList a:active { - text-decoration: none; - color: #333; -} - -/* Nav item */ -.navItem { - display: flex; - align-items: center; - padding: 10px 14px; - border-radius: 6px; - margin-bottom: 6px; - font-weight: normal; - transition: background-color .15s ease; -} - -/* Hover */ -.navItem:hover { - background: #eef2f4; -} - -/* Active (expanded only) */ -.navItem.active { - background: #e6f1f7; - font-weight: 600; - border-left: 3px solid #0b80b5; -} - -/* Icon */ -.glyph { - font-size: 18px; - width: 22px; - display: inline-flex; - justify-content: center; - margin-right: 12px; - color: #8c949a; -} - -/* Label */ -.navLabel { - white-space: nowrap; -} - -/* ========================================================= - COLLAPSED SIDEBAR (LEGACY BEHAVIOR) - ========================================================= */ - -/* Hide text only */ -.app.nav-collapsed .navLabel, -.app.nav-collapsed .logoText, -.app.nav-collapsed .navTitle, -.app.nav-collapsed .navGroupTitle { - display: none; -} - -.app.nav-collapsed .navGroup + .navGroup { - border-top: 1px solid #e5e7eb; - margin-top: 8px; - padding-top: 8px; -} - -/* Center icons */ -.app.nav-collapsed .navItem { - justify-content: center; - padding-left: 0; - padding-right: 0; -} - -.app.nav-collapsed .glyph { - margin-right: 0; -} - -/* 🔥 REMOVE ACTIVE STYLING WHEN COLLAPSED (CRITICAL FIX) */ -.app.nav-collapsed .navItem.active { - background: transparent; - border-left: none; - font-weight: normal; -} - -/* Hover only (legacy) */ -.app.nav-collapsed .navItem:hover { - background: #eef2f4; -} - -/* ========================================================= - MAIN AREA (NEVER MOVES) - ========================================================= */ -.mainArea { - flex: 1 1 auto; - min-width: 0; - min-height: 100vh; - display: flex; - flex-direction: column; -} - -/* ========================================================= - TOPBAR - ========================================================= */ -.topbar { - height: var(--topbar-h); - background: linear-gradient(var(--blue-main), var(--blue-dark)); - border-bottom: 1px solid #062c3e; - display: flex; - align-items: center; - justify-content: space-between; - padding: 0 16px; - color: #fff; -} - -.topLeft { - display: flex; - align-items: center; -} - -.hamburger { - background: none; - border: none; - color: #fff; - font-size: 18px; - cursor: pointer; - margin-right: 12px; -} - -.topRight { - display: flex; - align-items: center; - gap: 8px; -} - -.topbarIconBtn { - background: none; - border: none; - color: rgba(255,255,255,0.7); - font-size: 16px; - cursor: pointer; - padding: 6px 8px; - border-radius: 4px; -} - -.topbarIconBtn:hover { - color: #fff; - background: rgba(255,255,255,0.1); -} - -.topbarLogout { - color: rgba(255,255,255,0.5); -} - -.topbarLogout:hover { - color: #ff6b6b; - background: rgba(255,100,100,0.1); -} - -.notifDropdown { - position: fixed; - background: #fff; - border: 1px solid #ddd; - border-radius: 8px; - box-shadow: 0 8px 24px rgba(0,0,0,0.15); - z-index: 99999; - width: 300px; - overflow: hidden; -} - -.notifHeader { - padding: 12px 16px; - font-size: 14px; - font-weight: 600; - color: #333; - border-bottom: 1px solid #eee; -} - -.notifEmpty { - display: flex; - flex-direction: column; - align-items: center; - gap: 8px; - padding: 30px 16px; - color: #999; - font-size: 13px; -} - -.notifEmptyIcon { - font-size: 24px; - color: #ccc; -} - -.topbarUserBtn { - display: flex; - align-items: center; - gap: 8px; - background: none; - border: none; - cursor: pointer; - padding: 4px 8px; - border-radius: 4px; -} - -.topbarUserBtn:hover { - background: rgba(255,255,255,0.1); -} - -.topbarUserCaret { - color: rgba(255,255,255,0.5); - font-size: 12px; - margin-left: 2px; -} - -.userDropdown { - position: fixed; - background: #fff; - border: 1px solid #ddd; - border-radius: 8px; - box-shadow: 0 8px 24px rgba(0,0,0,0.15); - z-index: 99999; - width: 260px; - overflow: hidden; -} - -.userDropdownHeader { - display: flex; - align-items: center; - gap: 12px; - padding: 16px; -} - -.userDropdownAvatar { - width: 40px; - height: 40px; - border-radius: 50%; - background: #1fa8de; - display: flex; - align-items: center; - justify-content: center; - font-weight: 600; - font-size: 14px; - color: #fff; - flex-shrink: 0; -} - -.userDropdownName { - font-size: 14px; - font-weight: 600; - color: #333; -} - -.userDropdownEmail { - font-size: 12px; - color: #999; -} - -.userDropdownDivider { - height: 1px; - background: #eee; -} - -.userDropdownItem { - display: flex; - align-items: center; - gap: 10px; - width: 100%; - padding: 10px 16px; - border: none; - background: #fff; - font-size: 13px; - color: #333; - cursor: pointer; - text-align: left; -} - -.userDropdownItem:hover { - background: #f5f5f5; -} - -.userDropdownItem i { - width: 16px; - text-align: center; - color: #999; -} - -.userDropdownLogout { - color: #dc3545; -} - -.userDropdownLogout:hover { - background: #fff5f5; -} - -.userDropdownLogout i { - color: #dc3545; -} - -.topbarDivider { - width: 1px; - height: 24px; - background: rgba(255,255,255,0.15); - margin: 0 4px; -} - -.topbarUser { - display: flex; - align-items: center; - gap: 8px; -} - -.avatar { - width: 32px; - height: 32px; - border-radius: 50%; - background: #1fa8de; - display: flex; - align-items: center; - justify-content: center; - font-weight: 600; - font-size: 12px; - color: #fff; - flex-shrink: 0; -} - -.topbarUserInfo { - display: flex; - flex-direction: column; - line-height: 1.2; -} - -.topbarUserName { - font-size: 13px; - font-weight: 600; - color: #fff; -} - -.topbarUserRole { - font-size: 11px; - color: rgba(255,255,255,0.5); -} - -/* ========================================================= - CONTENT - ========================================================= */ -.contentWrapper { - padding: 10px; -} - -/* ========================================================= - TABLE (CONTACTS / EMPLOYEES) - ========================================================= */ -.contactTable { - width: 100%; - border-collapse: collapse; -} - -.contactTable thead th { - padding: 10px 12px; - background: linear-gradient(#a9bdca, #8fa6b4); - color: #fff; - font-weight: 600; - border-bottom: 1px solid #7d96a6; -} - -.contactTable tbody td { - padding: 10px 12px; - border-bottom: 1px solid #eee; -} - -.contactTable tbody tr:nth-child(odd) { - background: #fff; -} - -.contactTable tbody tr:nth-child(even) { - background: #fbfbfb; -} - -.controlsTop { - display: flex; - align-items: center; - justify-content: space-between; - padding: 8px 12px; -} - -.leftControls { - display: flex; - align-items: center; -} - -.filter { - display: flex; - align-items: center; -} - -.filter select { - height: 34px; - border: 1px solid #ccc; - border-right: none; - border-radius: 4px 0 0 4px; - padding: 6px 8px; - background: #fff; -} - -.filter input { - height: 34px; - width: 260px; - border: 1px solid #ccc; - border-left: none; - border-right: none; - padding: 6px 10px; -} - -.searchBtn { - height: 34px; - padding: 0 14px; - border: 1px solid #0073aa; - background-color: #0073aa; - color: #fff; - border-radius: 0 4px 4px 0; - cursor: pointer; -} - -.searchBtn:hover { - background-color: #005f8d; -} - -.rightControls { - display: flex; - align-items: center; -} diff --git a/src/index.html b/src/index.html deleted file mode 100644 index 98499e9e..00000000 --- a/src/index.html +++ /dev/null @@ -1 +0,0 @@ -
\ No newline at end of file diff --git a/src/index.js b/src/index.js deleted file mode 100644 index be44fd46..00000000 --- a/src/index.js +++ /dev/null @@ -1,21 +0,0 @@ -import React from "react"; -import ReactDOM from "react-dom/client"; -import { BrowserRouter } from "react-router"; -import { Provider } from 'react-redux'; -import { QueryClientProvider } from '@tanstack/react-query'; -import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; -import { store } from './app/store'; -import { queryClient } from './lib/queryClient'; -import App from "./App"; -import "./index.css"; -import "./site.css"; - -const root = ReactDOM.createRoot(document.getElementById("root")); -root.render( - - - - {process.env.NODE_ENV === 'development' && } - - -); diff --git a/src/lib/api/client.js b/src/lib/api/client.js deleted file mode 100644 index 68bd5f55..00000000 --- a/src/lib/api/client.js +++ /dev/null @@ -1,79 +0,0 @@ -import axios from 'axios'; -import { API_URL } from '../../constants'; - -// Create axios instance -const apiClient = axios.create({ - baseURL: API_URL, - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, -}); - -// Request interceptor - Add auth token -apiClient.interceptors.request.use( - (config) => { - const tokenData = window.localStorage.getItem('token'); - if (tokenData) { - try { - const user = JSON.parse(tokenData); - if (user?.token) { - config.headers.Authorization = `Bearer ${user.token}`; - } - } catch (error) { - console.error('Error parsing token:', error); - } - } - return config; - }, - (error) => { - return Promise.reject(error); - } -); - -// Response interceptor - Handle errors globally -apiClient.interceptors.response.use( - (response) => { - // Handle different response structures from the API - // Some endpoints return { data: [...] }, some return plain arrays - return response; - }, - (error) => { - if (error.response) { - // The request was made and the server responded with a status code - // that falls out of the range of 2xx - const { status, data } = error.response; - - if (status === 401) { - // Unauthorized - redirect to login - window.localStorage.removeItem('token'); - window.location.href = '/login'; - return Promise.reject(new Error('You are not authorized to access this page.')); - } - - if (status === 403) { - return Promise.reject(new Error('You do not have permission to perform this action.')); - } - - if (status === 404) { - return Promise.reject(new Error('The requested resource was not found.')); - } - - if (status === 500) { - return Promise.reject(new Error('A server error occurred. Please try again later.')); - } - - // Extract error message from response - const message = data?.message || data?.Message || data?.error || 'An error occurred'; - return Promise.reject(new Error(message)); - } else if (error.request) { - // The request was made but no response was received - return Promise.reject(new Error('No response from server. Please check your connection.')); - } else { - // Something happened in setting up the request that triggered an Error - return Promise.reject(error); - } - } -); - -export default apiClient; diff --git a/src/lib/api/services.js b/src/lib/api/services.js deleted file mode 100644 index 12097781..00000000 --- a/src/lib/api/services.js +++ /dev/null @@ -1,268 +0,0 @@ -import apiClient from './client'; - -/** - * Generic API service factory - * Creates CRUD operations for any entity - */ -const createApiService = (endpoint) => ({ - getAll: (params) => apiClient.get(endpoint, { params }).then((res) => res.data), - - getById: (id) => apiClient.get(`${endpoint}/${id}`).then((res) => res.data), - - create: (data) => apiClient.post(endpoint, data).then((res) => res.data), - - update: (id, data) => apiClient.put(`${endpoint}/${id}`, data).then((res) => res.data), - - delete: (id) => apiClient.delete(`${endpoint}/${id}`).then((res) => res.data), -}); - -// Work Orders API -export const workOrdersApi = { - getList: (params) => - apiClient - .get('/WorkOrder/GetWorkOrderList', { params }) - .then((res) => { - const data = res.data?.data || res.data?.Data || res.data || []; - return { - items: Array.isArray(data) ? data : [], - totalCount: res.data?.totalCount || res.data?.TotalCount || data.length, - page: res.data?.pageNumber || res.data?.PageNumber || params?.page || 1, - pageSize: res.data?.pageSize || res.data?.PageSize || params?.pageSize || 12, - }; - }), - - getById: (id) => apiClient.get(`/WorkOrder/GetById?id=${id}`).then((res) => res.data), - - create: (data) => apiClient.post('/WorkOrder/Create', data).then((res) => res.data), - - update: (data) => apiClient.post('/WorkOrder/Update', data).then((res) => res.data), - - delete: (id) => apiClient.get(`/WorkOrder/DeleteWorkOrder?id=${id}`).then((res) => res.data), -}; - -// PM Schedules API -export const pmSchedulesApi = { - getList: (params) => - apiClient - .get('/PMSchedule/GetList', { params }) - .then((res) => { - // Handle both array responses and paginated responses - const data = res.data; - const items = Array.isArray(data) ? data : data?.value || data?.data || data?.Data || []; - - return { - items, - totalCount: data?.Count || data?.totalCount || items.length, - page: params?.page || 1, - pageSize: params?.pageSize || 12, - }; - }), - - getById: (id) => apiClient.get(`/PMSchedule/GetById?id=${id}`).then((res) => res.data), - - save: (data) => apiClient.post('/PMSchedule/Save', data).then((res) => res.data), - - delete: (id) => apiClient.post(`/PMSchedule/Delete?id=${id}`).then((res) => res.data), -}; - -// Follow Ups API -export const followUpsApi = { - getList: (params) => - apiClient - .get('/FollowUp/GetFollowUpList', { params }) - .then((res) => { - const data = res.data?.data || res.data?.Data || res.data || []; - return { - items: Array.isArray(data) ? data : [], - totalCount: res.data?.totalCount || res.data?.TotalCount || data.length, - }; - }), - - getById: (id) => apiClient.get(`/FollowUp/GetById?id=${id}`).then((res) => res.data), - - create: (data) => apiClient.post('/FollowUp/Create', data).then((res) => res.data), - - update: (data) => apiClient.post('/FollowUp/Update', data).then((res) => res.data), - - delete: (id) => apiClient.post(`/FollowUp/Delete?id=${id}`).then((res) => res.data), - - complete: (id) => apiClient.post(`/FollowUp/Complete?id=${id}`).then((res) => res.data), - - cancel: (id) => apiClient.post(`/FollowUp/Cancel?id=${id}`).then((res) => res.data), -}; - -// Employees API -export const employeesApi = { - getList: (params) => - apiClient - .get('/Employee/GetEmployeeList', { params }) - .then((res) => { - const data = res.data?.data || res.data?.Data || res.data || []; - return { - items: Array.isArray(data) ? data : [], - totalCount: res.data?.totalCount || res.data?.TotalCount || data.length, - }; - }), - - getById: (id) => apiClient.get(`/Employee/GetById?id=${id}`).then((res) => res.data), - - create: (data) => apiClient.post('/Employee/Create', data).then((res) => res.data), - - update: (data) => apiClient.post('/Employee/Update', data).then((res) => res.data), - - delete: (id) => apiClient.post(`/Employee/Delete?id=${id}`).then((res) => res.data), - - getDropdown: () => apiClient.get('/Employee/Dropdown').then((res) => res.data), -}; - -// Contacts API -export const contactsApi = { - getList: (params) => - apiClient - .get('/Contact/GetContactList', { params }) - .then((res) => { - const data = res.data?.data || res.data?.Data || res.data || []; - return { - items: Array.isArray(data) ? data : [], - totalCount: res.data?.totalCount || res.data?.TotalCount || data.length, - }; - }), - - getById: (id) => apiClient.get(`/Contact/GetById?id=${id}`).then((res) => res.data), - - create: (data) => apiClient.post('/Contact/Create', data).then((res) => res.data), - - update: (data) => apiClient.post('/Contact/Update', data).then((res) => res.data), - - delete: (id) => apiClient.post(`/Contact/Delete?id=${id}`).then((res) => res.data), - - getDropdown: (locationId) => - apiClient.get(`/Contact/Dropdown?locationId=${locationId}`).then((res) => res.data), -}; - -// Locations API -export const locationsApi = { - getList: (params) => - apiClient.get('/Location/GetLocationList', { params }).then((res) => { - const data = res.data?.data || res.data?.Data || res.data || []; - return { - items: Array.isArray(data) ? data : [], - totalCount: res.data?.totalCount || res.data?.TotalCount || data.length, - }; - }), - - getById: (id) => apiClient.get(`/Location/GetById?id=${id}`).then((res) => res.data), - - create: (data) => apiClient.post('/Location/Create', data).then((res) => res.data), - - update: (data) => apiClient.post('/Location/Update', data).then((res) => res.data), - - delete: (id) => apiClient.post(`/Location/Delete?id=${id}`).then((res) => res.data), - - getDropdown: (accountId) => - apiClient.get(`/Location/Dropdown?accountId=${accountId || ''}`).then((res) => res.data), -}; - -// Accounts API -export const accountsApi = { - getList: (params) => - apiClient.get('/Account/GetAccountList', { params }).then((res) => { - const data = res.data?.data || res.data?.Data || res.data || []; - return { - items: Array.isArray(data) ? data : [], - totalCount: res.data?.totalCount || res.data?.TotalCount || data.length, - }; - }), - - getById: (id) => apiClient.get(`/Account/GetById?id=${id}`).then((res) => res.data), - - create: (data) => apiClient.post('/Account/Create', data).then((res) => res.data), - - update: (data) => apiClient.post('/Account/Update', data).then((res) => res.data), - - delete: (id) => apiClient.post(`/Account/Delete?id=${id}`).then((res) => res.data), - - getDropdown: () => apiClient.get('/Account/Dropdown').then((res) => res.data), -}; - -// Assets API -export const assetsApi = { - getList: (params) => - apiClient.get('/Asset/GetAssetList', { params }).then((res) => { - const data = res.data?.data || res.data?.Data || res.data || []; - return { - items: Array.isArray(data) ? data : [], - totalCount: res.data?.totalCount || res.data?.TotalCount || data.length, - }; - }), - - getById: (id) => apiClient.get(`/Asset/GetById?id=${id}`).then((res) => res.data), - - create: (data) => apiClient.post('/Asset/Create', data).then((res) => res.data), - - update: (data) => apiClient.post('/Asset/Update', data).then((res) => res.data), - - delete: (id) => apiClient.post(`/Asset/Delete?id=${id}`).then((res) => res.data), -}; - -// Vendors API -export const vendorsApi = { - getList: (params) => - apiClient.get('/Vendor/GetVendorList', { params }).then((res) => { - const data = res.data?.data || res.data?.Data || res.data || []; - return { - items: Array.isArray(data) ? data : [], - totalCount: res.data?.totalCount || res.data?.TotalCount || data.length, - }; - }), - - getById: (id) => apiClient.get(`/Vendor/GetById?id=${id}`).then((res) => res.data), - - create: (data) => apiClient.post('/Vendor/Create', data).then((res) => res.data), - - update: (data) => apiClient.post('/Vendor/Update', data).then((res) => res.data), - - delete: (id) => apiClient.post(`/Vendor/Delete?id=${id}`).then((res) => res.data), - - getDropdown: (params) => - apiClient.get('/Vendor/Dropdown', { params }).then((res) => res.data), -}; - -// Dashboard API -export const dashboardApi = { - getStats: () => apiClient.get('/Dashboard/Stats').then((res) => res.data), -}; - -// Dropdown Options API -export const dropdownApi = { - getByCategory: (category, parent) => { - const params = parent ? { parent } : {}; - return apiClient - .get(`/DropdownOptions/ByCategory/${encodeURIComponent(category)}`, { params }) - .then((res) => res.data); - }, - - create: (data) => apiClient.post('/DropdownOptions/Create', data).then((res) => res.data), - - update: (data) => apiClient.post('/DropdownOptions/Update', data).then((res) => res.data), - - delete: (id) => apiClient.delete(`/DropdownOptions/${id}`).then((res) => res.data), -}; - -// Authentication API -export const authApi = { - login: (credentials) => - apiClient.post('/Authentication/Login', credentials).then((res) => res.data), - - changePassword: (data) => - apiClient.post('/Authentication/ChangePassword', data).then((res) => res.data), - - forgotPassword: (email) => - apiClient.post(`/Authentication/ForgetPassword?Email=${encodeURIComponent(email)}`).then((res) => res.data), - - verifyResetCode: (code) => - apiClient.post(`/Authentication/VerificationCode?code=${encodeURIComponent(code)}`).then((res) => res.data), - - resetPassword: (data) => - apiClient.post('/Authentication/ResetPassword', data).then((res) => res.data), -}; diff --git a/src/lib/queryClient.js b/src/lib/queryClient.js deleted file mode 100644 index ec1a63c6..00000000 --- a/src/lib/queryClient.js +++ /dev/null @@ -1,25 +0,0 @@ -import { QueryClient } from '@tanstack/react-query'; -import { toast } from 'react-toastify'; - -export const queryClient = new QueryClient({ - defaultOptions: { - queries: { - staleTime: 5 * 60 * 1000, // 5 minutes - cacheTime: 10 * 60 * 1000, // 10 minutes - retry: 1, - refetchOnWindowFocus: false, - onError: (error) => { - console.error('Query error:', error); - toast.error(error.message || 'An error occurred while fetching data'); - }, - }, - mutations: { - onError: (error) => { - console.error('Mutation error:', error); - toast.error(error.message || 'An error occurred while updating data'); - }, - }, - }, -}); - -export default queryClient; diff --git a/src/pages/Dashboard.css b/src/pages/Dashboard.css deleted file mode 100644 index aeb19af4..00000000 --- a/src/pages/Dashboard.css +++ /dev/null @@ -1,122 +0,0 @@ -/* Dashboard Styles */ -.dashboardPage { - padding: 20px; -} - -.pageTitle { - font-size: 24px; - font-weight: 600; - margin-bottom: 20px; - color: #333; -} - -.kpiContainer { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); - gap: 15px; - margin-bottom: 30px; -} - -.kpiCard { - padding: 20px; - border-radius: 4px; - color: white; - display: flex; - align-items: center; - gap: 15px; - min-height: 100px; - cursor: pointer; - transition: filter 0.15s; -} - -.kpiCard:hover { - filter: brightness(0.9); -} - -.bg-success { background-color: #28a745; } -.bg-primary { background-color: #007bff; } -.bg-info { background-color: #17a2b8; } -.bg-warning { background-color: #ffc107; color: #333; } -.bg-danger { background-color: #dc3545; } - -.kpiIcon { - font-size: 32px; - min-width: 50px; -} - -.kpiContent h5 { - margin: 0 0 10px 0; - font-size: 16px; - font-weight: 600; -} - -.kpiContent p { - margin: 0; - font-size: 14px; - opacity: 0.9; -} - -.kpiStat { - font-size: 24px; - font-weight: bold; -} - -.card { - background: white; - border: 1px solid #ddd; - border-radius: 4px; - padding: 0; - margin-bottom: 20px; -} - -.mt-4 { margin-top: 30px; } - -.cardHeader { - padding: 20px; - border-bottom: 1px solid #ddd; -} - -.cardHeader h3 { - margin: 0 0 5px 0; - font-size: 18px; - color: #333; -} - -.cardHeader p { - margin: 0; - font-size: 13px; - color: #666; -} - -.tableWrapper { - padding: 20px; -} - -.dashTable { - width: 100%; - border-collapse: collapse; - font-size: 14px; -} - -.dashTable th { - background: #f5f5f5; - border: 1px solid #ddd; - padding: 12px; - text-align: left; - font-weight: 600; - color: #333; -} - -.dashTable td { - border: 1px solid #ddd; - padding: 10px 12px; - color: #333; -} - -.dashTable tbody tr:hover { - background: #f9f9f9; -} - -.text-right { - text-align: right; -} diff --git a/src/pages/Dashboard.js b/src/pages/Dashboard.js deleted file mode 100644 index 1cd26e0e..00000000 --- a/src/pages/Dashboard.js +++ /dev/null @@ -1,131 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { useNavigate } from "react-router-dom"; -import { get } from "../apiUtil"; -import { API_URL } from "../constants"; -import "./Dashboard.css"; - -const Dashboard = () => { - const navigate = useNavigate(); - const [stats, setStats] = useState(null); - const [loading, setLoading] = useState(true); - - useEffect(() => { - get(`${API_URL}/Dashboard/Stats`) - .then((data) => setStats(data)) - .catch(() => {}) - .finally(() => setLoading(false)); - }, []); - - const kpis = [ - { - title: "Create a Work Order", - icon: "fa-plus", - color: "success", - desc: "Report a problem you need fixed", - onClick: () => navigate("/workorders/new"), - }, - { - title: "Open Work Orders", - icon: "fa-folder-open", - color: "primary", - stat: loading ? "..." : stats?.open ?? 0, - onClick: () => navigate("/workorders"), - }, - { - title: "Not Dispatched", - icon: "fa-clock-o", - color: "info", - stat: loading ? "..." : stats?.notDispatched ?? 0, - onClick: () => navigate("/workorders"), - }, - { - title: "Completed Orders", - icon: "fa-check-circle", - color: "warning", - stat: loading ? "..." : stats?.completed ?? 0, - onClick: () => navigate("/workorders"), - }, - ]; - - return ( -
-
Dashboard
- -
- {kpis.map((kpi, idx) => ( -
-
- -
-
-
{kpi.title}
- {kpi.stat !== undefined &&
{kpi.stat?.toLocaleString?.() ?? kpi.stat}
} - {kpi.desc &&

{kpi.desc}

} -
-
- ))} -
- -
-
-

Priority Analysis

-

Age of open Work Orders by Priority

-
-
- - - - - - - - - - - - - {[5, 4, 3, 2, 1].map((p) => ( - - - - - - - - - ))} - -
PriorityTotal0-14 Days15-29 Days30-59 Days60+ Days
{p}—————
-
-
- -
-
-

Invoice Analysis

-
-
- - - - - - - - - - {["Draft", "Challenged", "Pending Approval", "Approved", "Paid"].map((s) => ( - - - - - - ))} - -
StatusCountAmount
{s}0$0.00
-
-
-
- ); -}; - -export default Dashboard; diff --git a/src/pages/PmSchedule/api.js b/src/pages/PmSchedule/api.js deleted file mode 100644 index a79e59f9..00000000 --- a/src/pages/PmSchedule/api.js +++ /dev/null @@ -1,41 +0,0 @@ -import { get, post } from "../../apiUtil"; -import { API_URL } from "../../constants"; - -export const getPMSchedules = async () => { - const response = await get(`${API_URL}/PMSchedule/GetList`); - return Array.isArray(response) ? response : (response?.value || response?.data || response?.Data || []); -}; - -export const getPMScheduleById = async (id) => { - return get(`${API_URL}/PMSchedule/GetById?id=${id}`); -}; - -export const addPMSchedule = async (pmSchedule) => { - const payload = { - Id: 0, - Name: pmSchedule.assetName || pmSchedule.name, - Frequency: pmSchedule.frequency, - StartDate: pmSchedule.nextServiceDate || pmSchedule.startDate, - IsActive: pmSchedule.status === "Active" || pmSchedule.isActive !== false, - AssetId: pmSchedule.assetId || null, - LocationId: pmSchedule.locationId || null - }; - return post(`${API_URL}/PMSchedule/Save`, payload); -}; - -export const editPMSchedule = async (pmSchedule) => { - const payload = { - Id: pmSchedule.id, - Name: pmSchedule.assetName || pmSchedule.name, - Frequency: pmSchedule.frequency, - StartDate: pmSchedule.nextServiceDate || pmSchedule.startDate, - IsActive: pmSchedule.status === "Active" || pmSchedule.isActive !== false, - AssetId: pmSchedule.assetId || null, - LocationId: pmSchedule.locationId || null - }; - return post(`${API_URL}/PMSchedule/Save`, payload); -}; - -export const deletePMSchedule = async (id) => { - return post(`${API_URL}/PMSchedule/Delete?id=${id}`); -}; diff --git a/src/pages/PmSchedule/edit/api.js b/src/pages/PmSchedule/edit/api.js deleted file mode 100644 index 7da1e39c..00000000 --- a/src/pages/PmSchedule/edit/api.js +++ /dev/null @@ -1,6 +0,0 @@ -import { post } from "../../../apiUtil"; -import { API_URL } from "../../../constants"; - -export const editEmployee = async (payload) => { - return post(`${API_URL}/Employee/UpdateEmployee`, payload); -}; diff --git a/src/pages/PmSchedule/form/PmScheduleFormPage.css b/src/pages/PmSchedule/form/PmScheduleFormPage.css deleted file mode 100644 index 36081949..00000000 --- a/src/pages/PmSchedule/form/PmScheduleFormPage.css +++ /dev/null @@ -1,289 +0,0 @@ -/* PM Schedule Form Page - Matching Employee Form Style */ -.pmScheduleFormPage { - background: #f5f5f5; - min-height: 100vh; - padding: 0; -} - -.panel { - margin-bottom: 20px; - background-color: #fff; - border: 1px solid transparent; - border-radius: 4px; - box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); -} - -.panel-default { - border-color: #ddd; -} - -.panel-connected { - border-radius: 0; - border: none; - box-shadow: none; -} - -.panel-body { - padding: 15px; -} - -/* Breadcrumb */ -.breadcrumb { - padding: 8px 15px; - margin-bottom: 20px; - list-style: none; - background-color: #f5f5f5; - border-radius: 4px; -} - -.breadcrumb-alt { - background-color: white; - border-bottom: 1px solid #e5e5e5; - padding: 10px 20px; - margin: 0 0 20px 0; - border-radius: 0; -} - -.breadcrumb > li { - display: inline-block; -} - -.breadcrumb > li + li:before { - content: ">\00a0"; - padding: 0 5px; - color: #ccc; -} - -.breadcrumb > li a { - color: #0088cc; - text-decoration: none; -} - -.breadcrumb > li a:hover { - text-decoration: underline; -} - -.breadcrumb > .active { - color: #00a0d1; - font-weight: bold; - cursor: default; -} - -/* Container */ -.container { - padding-right: 15px; - padding-left: 15px; - margin-right: auto; - margin-left: auto; -} - -/* Form Header */ -.header { - margin-bottom: 15px; -} - -.heading { - margin: 0 0 10px 0; - font-weight: 300; - line-height: 1.1; - color: #333; -} - -.heading.thin { - font-weight: 300; - font-size: 24px; - display: flex; - align-items: center; - gap: 10px; -} - -.heading.text-uppercase { - text-transform: uppercase; - font-size: 14px; - margin-bottom: 15px; -} - -.text-primary { - color: #0088cc; -} - -.fa-stack { - position: relative; - display: inline-block; - width: 2em; - height: 2em; - line-height: 2em; - vertical-align: middle; -} - -.fa-stack-1x, -.fa-stack-2x { - position: absolute; - left: 0; - width: 100%; - text-align: center; -} - -.fa-stack-2x { - font-size: 2em; -} - -.fa-stack-1x { - font-size: 1em; - line-height: inherit; -} - -.fa-inverse { - color: #fff; -} - -/* Form Groups */ -.form-group { - margin-bottom: 15px; - position: relative; -} - -/* Row and Columns */ -.row { - margin-right: -15px; - margin-left: -15px; - display: flex; - flex-wrap: wrap; -} - -.row::after { - content: ""; - display: table; - clear: both; -} - -[class*="col-"] { - position: relative; - min-height: 1px; - padding-right: 15px; - padding-left: 15px; -} - -.col-sm-4 { - width: 33.33333333%; -} - -.col-sm-6 { - width: 50%; -} - -@media (min-width: 992px) { - .col-md-4 { - width: 33.33333333%; - } -} - -/* Labels */ -label { - display: inline-block; - max-width: 100%; - margin-bottom: 5px; - font-weight: 600; - font-size: 13px; - color: #333; -} - -.required-field::after { - content: " *"; - color: #d9534f; -} - -/* Form Controls */ -.form-control { - display: block; - width: 100%; - height: 34px; - padding: 6px 12px; - font-size: 14px; - line-height: 1.42857143; - color: #555; - background-color: #fff; - background-image: none; - border: 1px solid #ccc; - border-radius: 4px; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; - box-sizing: border-box; -} - -.form-control:focus { - border-color: #66afe9; - outline: 0; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6); -} - -select.form-control { - height: 34px; - padding: 6px 12px; -} - -textarea.form-control { - height: auto; - min-height: 100px; -} - -/* Form Actions */ -.form-actions { - padding: 19px 20px 20px; - margin-top: 20px; - margin-bottom: 20px; - border-top: 1px solid #e5e5e5; - text-align: right; -} - -/* Buttons */ -.btn { - display: inline-block; - padding: 6px 12px; - margin-bottom: 0; - font-size: 14px; - font-weight: 400; - line-height: 1.42857143; - text-align: center; - white-space: nowrap; - vertical-align: middle; - cursor: pointer; - border: 1px solid transparent; - border-radius: 4px; - user-select: none; -} - -.btn-default { - color: #333; - background-color: #fff; - border-color: #ccc; -} - -.btn-default:hover { - background-color: #e6e6e6; - border-color: #adadad; -} - -.btn-primary { - color: #fff; - background-color: #0088cc; - border-color: #0088cc; -} - -.btn-primary:hover { - background-color: #006699; - border-color: #005580; -} - -.btn:disabled { - cursor: not-allowed; - opacity: 0.65; -} - -/* Responsive */ -@media (max-width: 767px) { - [class*="col-sm-"] { - width: 100%; - } -} - diff --git a/src/pages/PmSchedule/form/PmScheduleFormPage.js b/src/pages/PmSchedule/form/PmScheduleFormPage.js deleted file mode 100644 index d3be7e51..00000000 --- a/src/pages/PmSchedule/form/PmScheduleFormPage.js +++ /dev/null @@ -1,303 +0,0 @@ -import React, { useState, useEffect } from "react"; -import { useParams, useNavigate } from "react-router"; -import { toast } from "react-toastify"; -import { getPMScheduleById, addPMSchedule, editPMSchedule } from "../api"; -import "./PmScheduleFormPage.css"; - -export default function PmScheduleFormPage() { - const { id } = useParams(); - const navigate = useNavigate(); - const isEdit = !!id; - - const [formData, setFormData] = useState({ - asset: "", - assetName: "", - frequency: "Quarterly", - lastServiceDate: "", - nextServiceDate: "", - status: "Active", - assignedTo: "", - notes: "", - }); - - const [loading, setLoading] = useState(false); - - useEffect(() => { - if (isEdit && id) { - const loadPMSchedule = async () => { - try { - const pm = await getPMScheduleById(id); - if (pm) { - setFormData(prev => { - const merged = { ...prev }; - for (const key of Object.keys(merged)) { - if (pm[key] != null) merged[key] = pm[key]; - } - return merged; - }); - } - } catch (err) { - console.error("Failed to load PM schedule", err); - toast.error("Failed to load PM schedule"); - } - }; - loadPMSchedule(); - } - }, [id, isEdit]); - - const handleChange = (e) => { - const { name, value } = e.target; - setFormData(prev => ({ - ...prev, - [name]: value, - })); - }; - - const handleSave = async (e) => { - e.preventDefault(); - setLoading(true); - - try { - if (isEdit) { - await editPMSchedule(formData); - toast.success("PM Schedule updated successfully!"); - } else { - await addPMSchedule(formData); - toast.success("PM Schedule created successfully!"); - } - navigate("/pmschedules"); - } catch (err) { - console.error(err); - toast.error("Failed to save PM Schedule"); - } finally { - setLoading(false); - } - }; - - const handleCancel = () => { - navigate("/pmschedules"); - }; - - return ( -
-
-
-
    -
  1. - { e.preventDefault(); navigate("/pmschedules"); }}> - PM Schedules - -
  2. -
  3. {isEdit ? "Edit" : "Create"}
  4. -
- -
-
- {/* Page Header */} -
-
-

- - - - - {isEdit ? "Edit a PM Schedule" : "Create a PM Schedule"} -

-
-
- - {/* Asset Information Section */} -
-
-
- Asset Information -
-
-
-
-
- - -
-
-
-
- - -
-
-
-
- - {/* Schedule Information Section */} -
-
-
- Schedule Information -
-
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - {/* Assignment Section */} -
-
-
- Assignment -
-
-
-
-
- - -
-
-
-
- - -
-
-
-
- - {/* Notes Section */} -
-
-
- Notes -
-
-
-
-
- -