@accounter/client 0.0.8-alpha-20251022034712-a30a030f5a83f0dff0ee9fa8ccd0ebdaf7986647 → 0.0.8-alpha-20251022131500-0d2446e83760934b05cf2c65a8b065989f7724c8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -1
- package/dist/assets/Checkbox-D7nOeER7.js +6 -0
- package/dist/assets/Progress-_KQ8BRrQ.js +1 -0
- package/dist/assets/Typography-BYHNiKxL.js +1 -0
- package/dist/assets/accordion-aunUrlum.js +1 -0
- package/dist/assets/accountant-approvals-1lH0z-Rv.js +1 -0
- package/dist/assets/all-charges-Cngljy2T.js +1 -0
- package/dist/assets/arrow-up-down-De1dsSxL.js +6 -0
- package/dist/assets/business-7l6LGFQt.js +37 -0
- package/dist/assets/business-transactions-single-BmOnrzw-.js +1 -0
- package/dist/assets/business-trip-BH8ZEeSB.js +1 -0
- package/dist/assets/charges-filters-DkzCOY0r.js +1 -0
- package/dist/assets/charges-ledger-validation-tNA_f_7H.js +1 -0
- package/dist/assets/chart-BJ85ZFS0.js +74 -0
- package/dist/assets/data-table-pagination-_iPJtRUP.js +11 -0
- package/dist/assets/editable-business-trip-DCtXJ6w4.js +16 -0
- package/dist/assets/graphql-document-dedupe-fragments-ByT8-wlV.js +1 -0
- package/dist/assets/index-6W3Ndi5M.js +1 -0
- package/dist/assets/index-A086__I6.js +1 -0
- package/dist/assets/index-AoVRWBXr.js +6 -0
- package/dist/assets/index-BGZNb2wc.js +17 -0
- package/dist/assets/index-BHUgtFG6.js +1 -0
- package/dist/assets/index-BQw927FW.js +11 -0
- package/dist/assets/index-C27oA70V.js +1 -0
- package/dist/assets/index-CIqGQ8uI.js +1 -0
- package/dist/assets/index-CMXNoVEJ.js +24 -0
- package/dist/assets/index-CQEXsBvi.js +1 -0
- package/dist/assets/index-CRWwjUSx.js +1 -0
- package/dist/assets/index-CUPkGo8z.js +1 -0
- package/dist/assets/index-Ch3veRcP.js +1 -0
- package/dist/assets/index-Cll1w4iD.js +6 -0
- package/dist/assets/index-Cnf3x_0g.js +2 -0
- package/dist/assets/index-DCiQggcN.js +9 -0
- package/dist/assets/index-DFO0fSvK.js +876 -0
- package/dist/assets/index-DLrRdx1l.js +1 -0
- package/dist/assets/index-DgOX69C5.js +1 -0
- package/dist/assets/index-DqPz6G2w.js +2 -0
- package/dist/assets/index-DxgUoyCT.js +1 -0
- package/dist/assets/index-Dxkz1HG4.js +137 -0
- package/dist/assets/index-LwYKcUCw.js +1 -0
- package/dist/assets/index-gdTXrWXt.css +1 -0
- package/dist/assets/{index.es-CWwhWGxX.js → index.es-g2vV-Mpx.js} +5 -5
- package/dist/assets/issue-document-DlwQP2Xx.js +1 -0
- package/dist/assets/login-page-C2voZ_Kj.js +1 -0
- package/dist/assets/missing-info-charges-eJYTfSDz.js +1 -0
- package/dist/assets/page-not-found-t6EEvRUF.js +1 -0
- package/dist/assets/pencil-nACAiGf5.js +6 -0
- package/dist/assets/report-commentary-row-BeCkGfXh.js +1 -0
- package/dist/assets/save-DIy7YBvX.js +11 -0
- package/dist/assets/sequential-CAnleQny.js +1 -0
- package/dist/assets/similar-charges-by-business-modal-BTts1hyT.js +1 -0
- package/dist/assets/sub-CNmFodmJ.js +1 -0
- package/dist/assets/subMonths-BkHf0iFx.js +1 -0
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/components/business-transactions/business-extended-info.tsx +13 -15
- package/src/components/business-transactions/business-transactions-single.tsx +3 -3
- package/src/components/business-transactions/index.tsx +12 -1
- package/src/components/business-trips/business-trip.tsx +3 -3
- package/src/components/charges/cells/business-trip.tsx +6 -8
- package/src/components/charges/cells/counterparty.tsx +7 -5
- package/src/components/common/accounter-table.tsx +6 -5
- package/src/components/common/business-trip-report/parts/core-expense-row.tsx +11 -9
- package/src/components/common/business-trip-report/parts/uncategorized-transactions.tsx +11 -13
- package/src/components/common/buttons/index.ts +0 -2
- package/src/components/common/buttons/logout-button.tsx +7 -6
- package/src/components/common/documents-to-charge-matcher/selection-handler/index.tsx +4 -2
- package/src/components/common/documents-to-charge-matcher/selection-handler/wide-filtered-selection.tsx +5 -7
- package/src/components/common/forms/edit-document.tsx +23 -10
- package/src/components/common/new-documents-list.tsx +10 -8
- package/src/components/documents-table/cells/creditor.tsx +11 -4
- package/src/components/documents-table/cells/debtor.tsx +11 -4
- package/src/components/error-boundary.tsx +189 -0
- package/src/components/layout/breadcrumbs.tsx +77 -0
- package/src/components/layout/dashboard-layout.tsx +4 -0
- package/src/components/layout/document-title.tsx +31 -0
- package/src/components/layout/navigation-progress.tsx +52 -0
- package/src/components/layout/page-skeleton.tsx +49 -0
- package/src/components/layout/sidelinks.tsx +28 -27
- package/src/components/ledger-table/counterparty-cell.tsx +19 -13
- package/src/components/login-page.tsx +2 -1
- package/src/components/reports/corporate-tax-ruling-compliance-report/index.tsx +3 -3
- package/src/components/reports/profit-and-loss-report/index.tsx +3 -3
- package/src/components/reports/tax-report/index.tsx +3 -3
- package/src/components/screens/businesses/business.tsx +21 -9
- package/src/components/screens/charges/charge.tsx +22 -9
- package/src/components/transactions-table/cells/counterparty.tsx +9 -2
- package/src/components/transactions-table/cells-legacy/counterparty.tsx +9 -2
- package/src/index.tsx +4 -22
- package/src/providers/auth-guard.tsx +14 -23
- package/src/providers/index.tsx +7 -2
- package/src/providers/urql-client.ts +86 -0
- package/src/providers/urql.tsx +7 -12
- package/src/providers/user-provider.tsx +3 -2
- package/src/router/config.tsx +534 -0
- package/src/router/layouts/dashboard-layout.tsx +20 -0
- package/src/router/layouts/root-layout.tsx +69 -0
- package/src/router/loaders/auth-loader.ts +32 -0
- package/src/router/loaders/business-loader.ts +25 -0
- package/src/router/loaders/charge-loader.ts +25 -0
- package/src/router/loaders/index.ts +17 -0
- package/src/router/routes.ts +88 -0
- package/src/router/types.ts +62 -0
- package/dist/assets/index-B2UYAO1O.css +0 -1
- package/dist/assets/index-BexxGuN6.js +0 -1224
- package/src/components/common/buttons/button-with-label.tsx +0 -41
- package/src/components/common/buttons/button.tsx +0 -44
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createClient,
|
|
3
|
+
fetchExchange,
|
|
4
|
+
mapExchange,
|
|
5
|
+
type AnyVariables,
|
|
6
|
+
type Client,
|
|
7
|
+
type Operation,
|
|
8
|
+
} from 'urql';
|
|
9
|
+
import { authExchange } from '@urql/exchange-auth';
|
|
10
|
+
import { UserService } from '../services/user-service.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Singleton URQL client for use in loaders and server-side operations
|
|
14
|
+
* This is separate from the Provider client to avoid React context dependencies
|
|
15
|
+
*/
|
|
16
|
+
let globalClient: Client | null = null;
|
|
17
|
+
|
|
18
|
+
export function getUrqlClient(): Client {
|
|
19
|
+
if (globalClient) {
|
|
20
|
+
return globalClient;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const authService = new UserService();
|
|
24
|
+
|
|
25
|
+
let url: string;
|
|
26
|
+
switch (import.meta.env.MODE) {
|
|
27
|
+
case 'production': {
|
|
28
|
+
url = 'https://accounter.onrender.com/graphql';
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
case 'staging': {
|
|
32
|
+
url = 'https://accounter-staging.onrender.com/graphql';
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
default: {
|
|
36
|
+
url = 'http://localhost:4000/graphql';
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
globalClient = createClient({
|
|
42
|
+
url,
|
|
43
|
+
exchanges: [
|
|
44
|
+
mapExchange({
|
|
45
|
+
onResult(result) {
|
|
46
|
+
if (result.error?.networkError) {
|
|
47
|
+
console.error('Network Error:', result.error.networkError);
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
}),
|
|
51
|
+
authExchange(async utils => {
|
|
52
|
+
return {
|
|
53
|
+
addAuthToOperation(operation): Operation<void, AnyVariables> {
|
|
54
|
+
const token = authService.authToken();
|
|
55
|
+
if (!token) {
|
|
56
|
+
return operation;
|
|
57
|
+
}
|
|
58
|
+
return utils.appendHeaders(operation, {
|
|
59
|
+
Authorization: token,
|
|
60
|
+
});
|
|
61
|
+
},
|
|
62
|
+
didAuthError(error, _operation): boolean {
|
|
63
|
+
return (
|
|
64
|
+
error?.response?.status === 401 ||
|
|
65
|
+
error?.graphQLErrors?.some(e => e.extensions?.code === 'FORBIDDEN')
|
|
66
|
+
);
|
|
67
|
+
},
|
|
68
|
+
async refreshAuth(): Promise<void> {
|
|
69
|
+
authService.logout();
|
|
70
|
+
// Redirect handled by route loader
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}),
|
|
74
|
+
fetchExchange,
|
|
75
|
+
],
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return globalClient;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Reset the global client (useful for tests or logout)
|
|
83
|
+
*/
|
|
84
|
+
export function resetUrqlClient(): void {
|
|
85
|
+
globalClient = null;
|
|
86
|
+
}
|
package/src/providers/urql.tsx
CHANGED
|
@@ -9,23 +9,23 @@ import {
|
|
|
9
9
|
type Operation,
|
|
10
10
|
} from 'urql';
|
|
11
11
|
import { authExchange } from '@urql/exchange-auth';
|
|
12
|
+
import { ROUTES } from '../router/routes.js';
|
|
12
13
|
import { AuthContext } from './auth-guard.js';
|
|
13
14
|
|
|
14
15
|
export function UrqlProvider({ children }: { children?: ReactNode }): ReactNode {
|
|
15
16
|
const { authService } = useContext(AuthContext);
|
|
16
17
|
const navigate = useNavigate();
|
|
18
|
+
|
|
19
|
+
// Track login state to trigger token updates
|
|
17
20
|
const loggedIn = authService.isLoggedIn();
|
|
18
21
|
|
|
19
22
|
const token = useMemo(() => {
|
|
20
23
|
const token = authService.authToken();
|
|
21
24
|
return token;
|
|
22
|
-
|
|
25
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- loggedIn is needed to trigger token refresh on login
|
|
26
|
+
}, [authService, loggedIn]);
|
|
23
27
|
|
|
24
28
|
const client = useMemo(() => {
|
|
25
|
-
if (!loggedIn) {
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
29
|
let url: string;
|
|
30
30
|
switch (import.meta.env.MODE) {
|
|
31
31
|
case 'production': {
|
|
@@ -47,16 +47,11 @@ export function UrqlProvider({ children }: { children?: ReactNode }): ReactNode
|
|
|
47
47
|
exchanges: [
|
|
48
48
|
mapExchange({
|
|
49
49
|
onResult(result) {
|
|
50
|
-
if (result.error?.networkError) {
|
|
51
|
-
console.error('Network Error:', result.error.networkError);
|
|
52
|
-
navigate('/network-error');
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
50
|
const isAuthError =
|
|
56
51
|
result?.error?.graphQLErrors.some(e => e.extensions?.code === 'FORBIDDEN') ||
|
|
57
52
|
result?.error?.response?.status === 401;
|
|
58
53
|
if (isAuthError) {
|
|
59
|
-
navigate(
|
|
54
|
+
navigate(ROUTES.LOGIN, {
|
|
60
55
|
state: { message: 'You are not authorized to access this page' },
|
|
61
56
|
});
|
|
62
57
|
}
|
|
@@ -86,7 +81,7 @@ export function UrqlProvider({ children }: { children?: ReactNode }): ReactNode
|
|
|
86
81
|
fetchExchange,
|
|
87
82
|
],
|
|
88
83
|
});
|
|
89
|
-
}, [
|
|
84
|
+
}, [navigate, token, authService]);
|
|
90
85
|
|
|
91
86
|
useEffect(() => {
|
|
92
87
|
if (!client) {
|
|
@@ -54,13 +54,14 @@ export function UserProvider({ children }: { children?: ReactNode }): ReactNode
|
|
|
54
54
|
});
|
|
55
55
|
|
|
56
56
|
// update active user
|
|
57
|
-
const currentUser = authService.currentUser();
|
|
58
57
|
useEffect(() => {
|
|
58
|
+
const currentUser = authService.currentUser();
|
|
59
59
|
if (!equal(currentUser, user)) {
|
|
60
60
|
setUser(currentUser);
|
|
61
61
|
fetchUserContext();
|
|
62
62
|
}
|
|
63
|
-
|
|
63
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- authService and fetchUserContext are stable
|
|
64
|
+
}, [user]);
|
|
64
65
|
|
|
65
66
|
// on user defaults fetched, update user defaults
|
|
66
67
|
useEffect(() => {
|
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
import { lazy, Suspense, type ReactElement } from 'react';
|
|
2
|
+
import type { RouteObject } from 'react-router-dom';
|
|
3
|
+
import { ErrorBoundary } from '../components/error-boundary.js';
|
|
4
|
+
import { PageSkeleton, ReportSkeleton, TableSkeleton } from '../components/layout/page-skeleton.js';
|
|
5
|
+
import { DashboardLayoutRoute } from './layouts/dashboard-layout.js';
|
|
6
|
+
import { RootLayout } from './layouts/root-layout.js';
|
|
7
|
+
import { businessLoader, chargeLoader, publicOnly, requireAuth } from './loaders/index.js';
|
|
8
|
+
import { ROUTES } from './routes.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Lazy load page components for code splitting
|
|
12
|
+
* Components use named exports, so we need to extract them
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// Charges
|
|
16
|
+
const AllCharges = lazy(() =>
|
|
17
|
+
import('../components/screens/charges/all-charges.js').then(m => ({ default: m.AllCharges })),
|
|
18
|
+
);
|
|
19
|
+
const MissingInfoCharges = lazy(() =>
|
|
20
|
+
import('../components/screens/charges/missing-info-charges.js').then(m => ({
|
|
21
|
+
default: m.MissingInfoCharges,
|
|
22
|
+
})),
|
|
23
|
+
);
|
|
24
|
+
const Charge = lazy(() =>
|
|
25
|
+
import('../components/screens/charges/charge.js').then(m => ({ default: m.Charge })),
|
|
26
|
+
);
|
|
27
|
+
const ChargesLedgerValidation = lazy(() =>
|
|
28
|
+
import('../components/charges-ledger-validation.js').then(m => ({
|
|
29
|
+
default: m.ChargesLedgerValidation,
|
|
30
|
+
})),
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
// Businesses
|
|
34
|
+
const Businesses = lazy(() =>
|
|
35
|
+
import('../components/businesses/index.js').then(m => ({ default: m.Businesses })),
|
|
36
|
+
);
|
|
37
|
+
const BusinessScreen = lazy(() =>
|
|
38
|
+
import('../components/screens/businesses/business.js').then(m => ({ default: m.BusinessScreen })),
|
|
39
|
+
);
|
|
40
|
+
const BusinessTransactionsSummery = lazy(() =>
|
|
41
|
+
import('../components/business-transactions/index.js').then(m => ({
|
|
42
|
+
default: m.BusinessTransactionsSummery,
|
|
43
|
+
})),
|
|
44
|
+
);
|
|
45
|
+
const BusinessTransactionsSingle = lazy(() =>
|
|
46
|
+
import('../components/business-transactions/business-transactions-single.js').then(m => ({
|
|
47
|
+
default: m.BusinessTransactionsSingle,
|
|
48
|
+
})),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// Business Trips
|
|
52
|
+
const BusinessTrips = lazy(() =>
|
|
53
|
+
import('../components/business-trips/index.js').then(m => ({ default: m.BusinessTrips })),
|
|
54
|
+
);
|
|
55
|
+
const BusinessTrip = lazy(() =>
|
|
56
|
+
import('../components/business-trips/business-trip.js').then(m => ({ default: m.BusinessTrip })),
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// Charts
|
|
60
|
+
const ChartPage = lazy(() =>
|
|
61
|
+
import('../components/charts/index.js').then(m => ({ default: m.ChartPage })),
|
|
62
|
+
);
|
|
63
|
+
const MonthlyIncomeExpenseChart = lazy(() =>
|
|
64
|
+
import('../components/charts/monthly-income-expense/index.js').then(m => ({
|
|
65
|
+
default: m.MonthlyIncomeExpenseChart,
|
|
66
|
+
})),
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
// Documents
|
|
70
|
+
const DocumentsReport = lazy(() =>
|
|
71
|
+
import('../components/screens/documents/all-documents/index.jsx').then(m => ({
|
|
72
|
+
default: m.DocumentsReport,
|
|
73
|
+
})),
|
|
74
|
+
);
|
|
75
|
+
const IssueDocumentScreen = lazy(() =>
|
|
76
|
+
import('../components/screens/documents/issue-document.js').then(m => ({
|
|
77
|
+
default: m.IssueDocumentScreen,
|
|
78
|
+
})),
|
|
79
|
+
);
|
|
80
|
+
const IssueDocuments = lazy(() =>
|
|
81
|
+
import('../components/screens/documents/issue-documents/index.js').then(m => ({
|
|
82
|
+
default: m.IssueDocuments,
|
|
83
|
+
})),
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
// Reports
|
|
87
|
+
const TrialBalanceReport = lazy(() =>
|
|
88
|
+
import('../components/reports/trial-balance-report/index.js').then(m => ({
|
|
89
|
+
default: m.TrialBalanceReport,
|
|
90
|
+
})),
|
|
91
|
+
);
|
|
92
|
+
const ContoReport = lazy(() =>
|
|
93
|
+
import('../components/reports/conto/index.js').then(m => ({ default: m.ContoReport })),
|
|
94
|
+
);
|
|
95
|
+
const VatMonthlyReport = lazy(() =>
|
|
96
|
+
import('../components/reports/vat-monthly-report/index.js').then(m => ({
|
|
97
|
+
default: m.VatMonthlyReport,
|
|
98
|
+
})),
|
|
99
|
+
);
|
|
100
|
+
const ProfitAndLossReport = lazy(() =>
|
|
101
|
+
import('../components/reports/profit-and-loss-report/index.js').then(m => ({
|
|
102
|
+
default: m.ProfitAndLossReport,
|
|
103
|
+
})),
|
|
104
|
+
);
|
|
105
|
+
const TaxReport = lazy(() =>
|
|
106
|
+
import('../components/reports/tax-report/index.js').then(m => ({ default: m.TaxReport })),
|
|
107
|
+
);
|
|
108
|
+
const DepreciationReport = lazy(() =>
|
|
109
|
+
import('../components/screens/reports/depreciation-report/index.js').then(m => ({
|
|
110
|
+
default: m.DepreciationReport,
|
|
111
|
+
})),
|
|
112
|
+
);
|
|
113
|
+
const Shaam6111Report = lazy(() =>
|
|
114
|
+
import('../components/screens/reports/shaam6111-report/index.js').then(m => ({
|
|
115
|
+
default: m.Shaam6111Report,
|
|
116
|
+
})),
|
|
117
|
+
);
|
|
118
|
+
const YearlyLedgerReport = lazy(() =>
|
|
119
|
+
import('../components/reports/yearly-ledger/index.js').then(m => ({
|
|
120
|
+
default: m.YearlyLedgerReport,
|
|
121
|
+
})),
|
|
122
|
+
);
|
|
123
|
+
const CorporateTaxRulingComplianceReport = lazy(() =>
|
|
124
|
+
import('../components/reports/corporate-tax-ruling-compliance-report/index.js').then(m => ({
|
|
125
|
+
default: m.CorporateTaxRulingComplianceReport,
|
|
126
|
+
})),
|
|
127
|
+
);
|
|
128
|
+
const BalanceReport = lazy(() =>
|
|
129
|
+
import('../components/screens/reports/balance-report/index.js').then(m => ({
|
|
130
|
+
default: m.BalanceReport,
|
|
131
|
+
})),
|
|
132
|
+
);
|
|
133
|
+
const ValidateReportsScreen = lazy(() =>
|
|
134
|
+
import('../components/reports/validations/index.js').then(m => ({
|
|
135
|
+
default: m.ValidateReportsScreen,
|
|
136
|
+
})),
|
|
137
|
+
);
|
|
138
|
+
const AccountantApprovals = lazy(() =>
|
|
139
|
+
import('../components/reports/accountant-approvals.js').then(m => ({
|
|
140
|
+
default: m.AccountantApprovals,
|
|
141
|
+
})),
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
// Other
|
|
145
|
+
const Salaries = lazy(() =>
|
|
146
|
+
import('../components/salaries/index.js').then(m => ({ default: m.Salaries })),
|
|
147
|
+
);
|
|
148
|
+
const TagsManager = lazy(() =>
|
|
149
|
+
import('../components/tags/index.js').then(m => ({ default: m.TagsManager })),
|
|
150
|
+
);
|
|
151
|
+
const TaxCategories = lazy(() =>
|
|
152
|
+
import('../components/tax-categories/index.js').then(m => ({ default: m.TaxCategories })),
|
|
153
|
+
);
|
|
154
|
+
const SortCodes = lazy(() =>
|
|
155
|
+
import('../components/screens/sort-codes/index.js').then(m => ({ default: m.SortCodes })),
|
|
156
|
+
);
|
|
157
|
+
const PageNotFound = lazy(() =>
|
|
158
|
+
import('../components/screens/page-not-found.js').then(m => ({ default: m.PageNotFound })),
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
// Auth
|
|
162
|
+
const LoginPage = lazy(() =>
|
|
163
|
+
import('../components/login-page.js').then(m => ({ default: m.LoginPage })),
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Helper to wrap components with Suspense
|
|
168
|
+
*/
|
|
169
|
+
function withSuspense(
|
|
170
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
171
|
+
Component: React.LazyExoticComponent<React.ComponentType<any>>,
|
|
172
|
+
fallback?: ReactElement,
|
|
173
|
+
) {
|
|
174
|
+
return (
|
|
175
|
+
<Suspense fallback={fallback || <PageSkeleton />}>
|
|
176
|
+
<Component />
|
|
177
|
+
</Suspense>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Application route configuration
|
|
183
|
+
* Using object-based routes for better type safety and features
|
|
184
|
+
*/
|
|
185
|
+
export const routes: RouteObject[] = [
|
|
186
|
+
{
|
|
187
|
+
path: '/',
|
|
188
|
+
element: <RootLayout />,
|
|
189
|
+
errorElement: <ErrorBoundary />,
|
|
190
|
+
children: [
|
|
191
|
+
// Public routes (login, error pages)
|
|
192
|
+
{
|
|
193
|
+
path: ROUTES.LOGIN,
|
|
194
|
+
loader: publicOnly,
|
|
195
|
+
element: withSuspense(LoginPage),
|
|
196
|
+
handle: {
|
|
197
|
+
title: 'Login',
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
// Protected routes (require authentication)
|
|
202
|
+
{
|
|
203
|
+
path: '/',
|
|
204
|
+
loader: requireAuth,
|
|
205
|
+
element: <DashboardLayoutRoute />,
|
|
206
|
+
errorElement: <ErrorBoundary />,
|
|
207
|
+
children: [
|
|
208
|
+
// Home / Charges (default)
|
|
209
|
+
{
|
|
210
|
+
index: true,
|
|
211
|
+
element: withSuspense(AllCharges, <TableSkeleton />),
|
|
212
|
+
handle: {
|
|
213
|
+
title: 'All Charges',
|
|
214
|
+
breadcrumb: 'Charges',
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
// Charges section
|
|
219
|
+
{
|
|
220
|
+
path: 'charges',
|
|
221
|
+
handle: { breadcrumb: 'Charges' },
|
|
222
|
+
children: [
|
|
223
|
+
{
|
|
224
|
+
index: true,
|
|
225
|
+
element: withSuspense(AllCharges, <TableSkeleton />),
|
|
226
|
+
handle: { title: 'All Charges' },
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
path: 'missing-info',
|
|
230
|
+
element: withSuspense(MissingInfoCharges, <TableSkeleton />),
|
|
231
|
+
handle: {
|
|
232
|
+
title: 'Missing Info Charges',
|
|
233
|
+
breadcrumb: 'Missing Info',
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
path: 'ledger-validation',
|
|
238
|
+
element: withSuspense(ChargesLedgerValidation, <TableSkeleton />),
|
|
239
|
+
handle: {
|
|
240
|
+
title: 'Ledger Validation',
|
|
241
|
+
breadcrumb: 'Ledger Validation',
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
path: ':chargeId',
|
|
246
|
+
loader: chargeLoader,
|
|
247
|
+
element: withSuspense(Charge),
|
|
248
|
+
handle: {
|
|
249
|
+
title: 'Charge Details',
|
|
250
|
+
breadcrumb: 'Details',
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
],
|
|
254
|
+
},
|
|
255
|
+
|
|
256
|
+
// Businesses section
|
|
257
|
+
{
|
|
258
|
+
path: 'businesses',
|
|
259
|
+
handle: { breadcrumb: 'Businesses' },
|
|
260
|
+
children: [
|
|
261
|
+
{
|
|
262
|
+
index: true,
|
|
263
|
+
element: withSuspense(Businesses, <TableSkeleton />),
|
|
264
|
+
handle: { title: 'All Businesses' },
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
path: 'transactions',
|
|
268
|
+
element: withSuspense(BusinessTransactionsSummery, <TableSkeleton />),
|
|
269
|
+
handle: {
|
|
270
|
+
title: 'Business Transactions Summary',
|
|
271
|
+
breadcrumb: 'Transactions',
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
path: ':businessId',
|
|
276
|
+
loader: businessLoader,
|
|
277
|
+
element: withSuspense(BusinessScreen),
|
|
278
|
+
handle: {
|
|
279
|
+
title: 'Business Details',
|
|
280
|
+
breadcrumb: 'Details',
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
path: ':businessId/transactions',
|
|
285
|
+
element: withSuspense(BusinessTransactionsSingle, <TableSkeleton />),
|
|
286
|
+
handle: {
|
|
287
|
+
title: 'Business Transactions',
|
|
288
|
+
breadcrumb: 'Transactions',
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
],
|
|
292
|
+
},
|
|
293
|
+
|
|
294
|
+
// Business Trips section
|
|
295
|
+
{
|
|
296
|
+
path: 'business-trips',
|
|
297
|
+
handle: { breadcrumb: 'Business Trips' },
|
|
298
|
+
children: [
|
|
299
|
+
{
|
|
300
|
+
index: true,
|
|
301
|
+
element: withSuspense(BusinessTrips, <TableSkeleton />),
|
|
302
|
+
handle: { title: 'Business Trips' },
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
path: ':businessTripId',
|
|
306
|
+
element: withSuspense(BusinessTrip),
|
|
307
|
+
handle: {
|
|
308
|
+
title: 'Business Trip Details',
|
|
309
|
+
breadcrumb: 'Details',
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
],
|
|
313
|
+
},
|
|
314
|
+
|
|
315
|
+
// Charts section
|
|
316
|
+
{
|
|
317
|
+
path: 'charts',
|
|
318
|
+
handle: { breadcrumb: 'Charts' },
|
|
319
|
+
children: [
|
|
320
|
+
{
|
|
321
|
+
index: true,
|
|
322
|
+
element: withSuspense(ChartPage),
|
|
323
|
+
handle: { title: 'Charts' },
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
path: 'monthly-income-expense',
|
|
327
|
+
element: withSuspense(MonthlyIncomeExpenseChart),
|
|
328
|
+
handle: {
|
|
329
|
+
title: 'Monthly Income/Expense',
|
|
330
|
+
breadcrumb: 'Income/Expense',
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
],
|
|
334
|
+
},
|
|
335
|
+
|
|
336
|
+
// Documents section
|
|
337
|
+
{
|
|
338
|
+
path: 'documents',
|
|
339
|
+
handle: { breadcrumb: 'Documents' },
|
|
340
|
+
children: [
|
|
341
|
+
{
|
|
342
|
+
index: true,
|
|
343
|
+
element: withSuspense(DocumentsReport, <TableSkeleton />),
|
|
344
|
+
handle: { title: 'All Documents' },
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
path: 'issue-document',
|
|
348
|
+
element: withSuspense(IssueDocumentScreen),
|
|
349
|
+
handle: {
|
|
350
|
+
title: 'Issue Document',
|
|
351
|
+
breadcrumb: 'Issue Document',
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
path: 'issue-documents',
|
|
356
|
+
element: withSuspense(IssueDocuments),
|
|
357
|
+
handle: {
|
|
358
|
+
title: 'Issue Documents',
|
|
359
|
+
breadcrumb: 'Issue Documents',
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
],
|
|
363
|
+
},
|
|
364
|
+
|
|
365
|
+
// Reports section
|
|
366
|
+
{
|
|
367
|
+
path: 'reports',
|
|
368
|
+
handle: { breadcrumb: 'Reports' },
|
|
369
|
+
children: [
|
|
370
|
+
{
|
|
371
|
+
path: 'trial-balance',
|
|
372
|
+
element: withSuspense(TrialBalanceReport, <ReportSkeleton />),
|
|
373
|
+
handle: {
|
|
374
|
+
title: 'Trial Balance Report',
|
|
375
|
+
breadcrumb: 'Trial Balance',
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
path: 'conto',
|
|
380
|
+
element: withSuspense(ContoReport, <ReportSkeleton />),
|
|
381
|
+
handle: {
|
|
382
|
+
title: 'Conto Report',
|
|
383
|
+
breadcrumb: 'Conto',
|
|
384
|
+
},
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
path: 'vat-monthly',
|
|
388
|
+
element: withSuspense(VatMonthlyReport, <ReportSkeleton />),
|
|
389
|
+
handle: {
|
|
390
|
+
title: 'VAT Monthly Report',
|
|
391
|
+
breadcrumb: 'VAT Monthly',
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
path: 'profit-and-loss',
|
|
396
|
+
handle: { breadcrumb: 'Profit & Loss' },
|
|
397
|
+
children: [
|
|
398
|
+
{
|
|
399
|
+
index: true,
|
|
400
|
+
element: withSuspense(ProfitAndLossReport, <ReportSkeleton />),
|
|
401
|
+
handle: { title: 'Profit & Loss Report' },
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
path: ':year',
|
|
405
|
+
element: withSuspense(ProfitAndLossReport, <ReportSkeleton />),
|
|
406
|
+
handle: {
|
|
407
|
+
title: 'Profit & Loss Report',
|
|
408
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- loader data type varies by route
|
|
409
|
+
breadcrumb: (data: any) => data?.year || 'Year',
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
],
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
path: 'tax',
|
|
416
|
+
handle: { breadcrumb: 'Tax' },
|
|
417
|
+
children: [
|
|
418
|
+
{
|
|
419
|
+
index: true,
|
|
420
|
+
element: withSuspense(TaxReport, <ReportSkeleton />),
|
|
421
|
+
handle: { title: 'Tax Report' },
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
path: ':year',
|
|
425
|
+
element: withSuspense(TaxReport, <ReportSkeleton />),
|
|
426
|
+
handle: {
|
|
427
|
+
title: 'Tax Report',
|
|
428
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- loader data type varies by route
|
|
429
|
+
breadcrumb: (data: any) => data?.year || 'Year',
|
|
430
|
+
},
|
|
431
|
+
},
|
|
432
|
+
],
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
path: 'depreciation',
|
|
436
|
+
element: withSuspense(DepreciationReport, <ReportSkeleton />),
|
|
437
|
+
handle: {
|
|
438
|
+
title: 'Depreciation Report',
|
|
439
|
+
breadcrumb: 'Depreciation',
|
|
440
|
+
},
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
path: 'shaam-6111',
|
|
444
|
+
element: withSuspense(Shaam6111Report, <ReportSkeleton />),
|
|
445
|
+
handle: {
|
|
446
|
+
title: 'Shaam 6111 Report',
|
|
447
|
+
breadcrumb: 'Shaam 6111',
|
|
448
|
+
},
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
path: 'yearly-ledger',
|
|
452
|
+
element: withSuspense(YearlyLedgerReport, <ReportSkeleton />),
|
|
453
|
+
handle: {
|
|
454
|
+
title: 'Yearly Ledger Report',
|
|
455
|
+
breadcrumb: 'Yearly Ledger',
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
path: 'corporate-tax-ruling-compliance',
|
|
460
|
+
handle: { breadcrumb: 'Tax Ruling' },
|
|
461
|
+
children: [
|
|
462
|
+
{
|
|
463
|
+
index: true,
|
|
464
|
+
element: withSuspense(CorporateTaxRulingComplianceReport, <ReportSkeleton />),
|
|
465
|
+
handle: { title: 'Corporate Tax Ruling Compliance Report' },
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
path: ':year',
|
|
469
|
+
element: withSuspense(CorporateTaxRulingComplianceReport, <ReportSkeleton />),
|
|
470
|
+
handle: {
|
|
471
|
+
title: 'Corporate Tax Ruling Compliance Report',
|
|
472
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- loader data type varies by route
|
|
473
|
+
breadcrumb: (data: any) => data?.year || 'Year',
|
|
474
|
+
},
|
|
475
|
+
},
|
|
476
|
+
],
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
path: 'balance',
|
|
480
|
+
element: withSuspense(BalanceReport, <ReportSkeleton />),
|
|
481
|
+
handle: {
|
|
482
|
+
title: 'Balance Report',
|
|
483
|
+
breadcrumb: 'Balance',
|
|
484
|
+
},
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
path: 'validate-reports',
|
|
488
|
+
element: withSuspense(ValidateReportsScreen),
|
|
489
|
+
handle: {
|
|
490
|
+
title: 'Validate Reports',
|
|
491
|
+
breadcrumb: 'Validate',
|
|
492
|
+
},
|
|
493
|
+
},
|
|
494
|
+
],
|
|
495
|
+
},
|
|
496
|
+
|
|
497
|
+
// Standalone routes
|
|
498
|
+
{
|
|
499
|
+
path: 'accountant-approvals',
|
|
500
|
+
element: withSuspense(AccountantApprovals, <TableSkeleton />),
|
|
501
|
+
handle: { title: 'Accountant Approvals', breadcrumb: 'Accountant Approvals' },
|
|
502
|
+
},
|
|
503
|
+
{
|
|
504
|
+
path: 'salaries',
|
|
505
|
+
element: withSuspense(Salaries, <TableSkeleton />),
|
|
506
|
+
handle: { title: 'Salaries', breadcrumb: 'Salaries' },
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
path: 'tags',
|
|
510
|
+
element: withSuspense(TagsManager),
|
|
511
|
+
handle: { title: 'Tags', breadcrumb: 'Tags' },
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
path: 'tax-categories',
|
|
515
|
+
element: withSuspense(TaxCategories),
|
|
516
|
+
handle: { title: 'Tax Categories', breadcrumb: 'Tax Categories' },
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
path: 'sort-codes',
|
|
520
|
+
element: withSuspense(SortCodes),
|
|
521
|
+
handle: { title: 'Sort Codes', breadcrumb: 'Sort Codes' },
|
|
522
|
+
},
|
|
523
|
+
|
|
524
|
+
// 404 catch-all
|
|
525
|
+
{
|
|
526
|
+
path: '*',
|
|
527
|
+
element: withSuspense(PageNotFound),
|
|
528
|
+
handle: { title: 'Page Not Found' },
|
|
529
|
+
},
|
|
530
|
+
],
|
|
531
|
+
},
|
|
532
|
+
],
|
|
533
|
+
},
|
|
534
|
+
];
|