datastake-daf 0.6.712 → 0.6.714
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/dist/hooks/index.js +92 -0
- package/dist/pages/index.js +15 -0
- package/dist/services/index.js +15 -0
- package/dist/style/datastake/datastake.css +2 -2
- package/dist/utils/index.js +122 -0
- package/package.json +1 -1
- package/src/@daf/core/components/Icon/configs/UnderDev.js +15 -0
- package/src/@daf/core/components/Screens/Admin/adminRoutes.js +1 -1
- package/src/@daf/hooks/useAdminDashboard.js +84 -0
- package/src/@daf/services/DashboardService.js +15 -0
- package/src/@daf/utils/adminConfigBuilders.js +102 -0
- package/src/helpers/StringHelper.js +6 -0
- package/src/hooks.js +2 -1
- package/src/styles/datastake/datastake.css +2 -2
- package/src/utils.js +8 -2
- package/src/@daf/core/components/Screens/Admin/AdminScreens/README.md +0 -216
package/dist/hooks/index.js
CHANGED
|
@@ -2224,6 +2224,21 @@ class DashboardService extends BaseService {
|
|
|
2224
2224
|
isApp: true
|
|
2225
2225
|
});
|
|
2226
2226
|
}
|
|
2227
|
+
getUserGrowth(activeFilter) {
|
|
2228
|
+
return this.apiGet({
|
|
2229
|
+
url: `/accounts/dashboard/user-growth`,
|
|
2230
|
+
isUserManager: true,
|
|
2231
|
+
params: {
|
|
2232
|
+
activeFilter
|
|
2233
|
+
}
|
|
2234
|
+
});
|
|
2235
|
+
}
|
|
2236
|
+
getAdminDashboard() {
|
|
2237
|
+
return this.apiGet({
|
|
2238
|
+
url: `/accounts/dashboard`,
|
|
2239
|
+
isUserManager: true
|
|
2240
|
+
});
|
|
2241
|
+
}
|
|
2227
2242
|
}
|
|
2228
2243
|
var DashboardService$1 = createLazyService(DashboardService);
|
|
2229
2244
|
|
|
@@ -2273,8 +2288,85 @@ const useWidgetFetch = ({
|
|
|
2273
2288
|
};
|
|
2274
2289
|
};
|
|
2275
2290
|
|
|
2291
|
+
/**
|
|
2292
|
+
* Generic hook for fetching admin dashboard data
|
|
2293
|
+
*
|
|
2294
|
+
* @param {Object} config - Configuration object
|
|
2295
|
+
* @param {Object} config.dashboardService - Service object with getDashboardData method
|
|
2296
|
+
* @param {Function} config.onError - Error handler function
|
|
2297
|
+
*
|
|
2298
|
+
* @returns {Object} Dashboard data and loading states
|
|
2299
|
+
*
|
|
2300
|
+
* @example
|
|
2301
|
+
* const { data, loading, userGrowthData, fetchUserGrowth } = useAdminDashboard({
|
|
2302
|
+
* dashboardService: {
|
|
2303
|
+
* getDashboardData: () => DashboardService.getAdminStats(),
|
|
2304
|
+
* getUserGrowth: (params) => DashboardService.getUserGrowth(params)
|
|
2305
|
+
* },
|
|
2306
|
+
* onError: (err) => console.error(err)
|
|
2307
|
+
* });
|
|
2308
|
+
*/
|
|
2309
|
+
function useAdminDashboard({
|
|
2310
|
+
dashboardService,
|
|
2311
|
+
onError
|
|
2312
|
+
}) {
|
|
2313
|
+
const [data, setData] = React.useState({});
|
|
2314
|
+
const [loading, setLoading] = React.useState(false);
|
|
2315
|
+
const [userGrowthData, setUserGrowthData] = React.useState([]);
|
|
2316
|
+
const [userGrowthDataLoading, setUserGrowthDataLoading] = React.useState(false);
|
|
2317
|
+
const fetchDashboardData = React.useCallback(async () => {
|
|
2318
|
+
if (!dashboardService?.getDashboardData) {
|
|
2319
|
+
console.warn("dashboardService.getDashboardData not provided");
|
|
2320
|
+
return;
|
|
2321
|
+
}
|
|
2322
|
+
setLoading(true);
|
|
2323
|
+
try {
|
|
2324
|
+
const response = await dashboardService.getDashboardData();
|
|
2325
|
+
setData(response || {});
|
|
2326
|
+
} catch (err) {
|
|
2327
|
+
if (onError) {
|
|
2328
|
+
onError(err);
|
|
2329
|
+
} else {
|
|
2330
|
+
console.error("Error fetching dashboard data:", err);
|
|
2331
|
+
}
|
|
2332
|
+
} finally {
|
|
2333
|
+
setLoading(false);
|
|
2334
|
+
}
|
|
2335
|
+
}, [dashboardService, onError]);
|
|
2336
|
+
const fetchUserGrowth = React.useCallback(async params => {
|
|
2337
|
+
if (!dashboardService?.getUserGrowth) {
|
|
2338
|
+
console.warn("dashboardService.getUserGrowth not provided");
|
|
2339
|
+
return;
|
|
2340
|
+
}
|
|
2341
|
+
setUserGrowthDataLoading(true);
|
|
2342
|
+
try {
|
|
2343
|
+
const response = await dashboardService.getUserGrowth(params);
|
|
2344
|
+
setUserGrowthData(response || []);
|
|
2345
|
+
} catch (err) {
|
|
2346
|
+
if (onError) {
|
|
2347
|
+
onError(err);
|
|
2348
|
+
} else {
|
|
2349
|
+
console.error("Error fetching user growth:", err);
|
|
2350
|
+
}
|
|
2351
|
+
} finally {
|
|
2352
|
+
setUserGrowthDataLoading(false);
|
|
2353
|
+
}
|
|
2354
|
+
}, [dashboardService, onError]);
|
|
2355
|
+
React.useEffect(() => {
|
|
2356
|
+
fetchDashboardData();
|
|
2357
|
+
}, [fetchDashboardData]);
|
|
2358
|
+
return {
|
|
2359
|
+
data,
|
|
2360
|
+
loading,
|
|
2361
|
+
userGrowthData,
|
|
2362
|
+
fetchUserGrowth,
|
|
2363
|
+
userGrowthDataLoading
|
|
2364
|
+
};
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2276
2367
|
exports.checkPermission = checkPermission;
|
|
2277
2368
|
exports.checkPermissionList = checkPermissionList;
|
|
2369
|
+
exports.useAdminDashboard = useAdminDashboard;
|
|
2278
2370
|
exports.useFilters = useFilters;
|
|
2279
2371
|
exports.useFirebase = useFirebase;
|
|
2280
2372
|
exports.useHeight = useHeight;
|
package/dist/pages/index.js
CHANGED
|
@@ -6322,6 +6322,21 @@ class DashboardService extends BaseService {
|
|
|
6322
6322
|
isApp: true
|
|
6323
6323
|
});
|
|
6324
6324
|
}
|
|
6325
|
+
getUserGrowth(activeFilter) {
|
|
6326
|
+
return this.apiGet({
|
|
6327
|
+
url: `/accounts/dashboard/user-growth`,
|
|
6328
|
+
isUserManager: true,
|
|
6329
|
+
params: {
|
|
6330
|
+
activeFilter
|
|
6331
|
+
}
|
|
6332
|
+
});
|
|
6333
|
+
}
|
|
6334
|
+
getAdminDashboard() {
|
|
6335
|
+
return this.apiGet({
|
|
6336
|
+
url: `/accounts/dashboard`,
|
|
6337
|
+
isUserManager: true
|
|
6338
|
+
});
|
|
6339
|
+
}
|
|
6325
6340
|
}
|
|
6326
6341
|
var DashboardService$1 = createLazyService(DashboardService);
|
|
6327
6342
|
|
package/dist/services/index.js
CHANGED
|
@@ -1200,6 +1200,21 @@ class DashboardService extends BaseService {
|
|
|
1200
1200
|
isApp: true
|
|
1201
1201
|
});
|
|
1202
1202
|
}
|
|
1203
|
+
getUserGrowth(activeFilter) {
|
|
1204
|
+
return this.apiGet({
|
|
1205
|
+
url: `/accounts/dashboard/user-growth`,
|
|
1206
|
+
isUserManager: true,
|
|
1207
|
+
params: {
|
|
1208
|
+
activeFilter
|
|
1209
|
+
}
|
|
1210
|
+
});
|
|
1211
|
+
}
|
|
1212
|
+
getAdminDashboard() {
|
|
1213
|
+
return this.apiGet({
|
|
1214
|
+
url: `/accounts/dashboard`,
|
|
1215
|
+
isUserManager: true
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1203
1218
|
}
|
|
1204
1219
|
var DashboardService$1 = createLazyService(DashboardService);
|
|
1205
1220
|
|
|
@@ -1542,13 +1542,13 @@ ul.ant-menu.ant-menu-dark.ant-menu-root.ant-menu-vertical::-webkit-scrollbar-thu
|
|
|
1542
1542
|
}
|
|
1543
1543
|
|
|
1544
1544
|
.components-layout:not(.nested) .sidenav-sider:not(.custom) .sidenav-cont .menus-cont.not-collapsed .sidemenu-cont .ant-menu.ant-menu-root.ant-menu-inline.ant-menu-dark
|
|
1545
|
-
.ant-menu-item:not(.ant-menu-item-selected) svg path
|
|
1545
|
+
.ant-menu-item:not(.ant-menu-item-selected):not(.ant-menu-item-disabled) svg path
|
|
1546
1546
|
{
|
|
1547
1547
|
stroke: #ffffff !important;
|
|
1548
1548
|
}
|
|
1549
1549
|
|
|
1550
1550
|
.components-layout:not(.nested) .sidenav-sider:not(.custom) .sidenav-cont .menus-cont.not-collapsed .sidemenu-cont .ant-menu.ant-menu-root.ant-menu-inline.ant-menu-dark
|
|
1551
|
-
.ant-menu-item:not(.ant-menu-item-selected) .ant-menu-title-content {
|
|
1551
|
+
.ant-menu-item:not(.ant-menu-item-selected):not(.ant-menu-item-disabled) .ant-menu-title-content {
|
|
1552
1552
|
color: #ffffff !important;
|
|
1553
1553
|
}
|
|
1554
1554
|
|
package/dist/utils/index.js
CHANGED
|
@@ -5127,6 +5127,9 @@ const cleanJSON = json => {
|
|
|
5127
5127
|
}
|
|
5128
5128
|
return json;
|
|
5129
5129
|
};
|
|
5130
|
+
function formatToKebabCase$1(str) {
|
|
5131
|
+
return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
5132
|
+
}
|
|
5130
5133
|
|
|
5131
5134
|
function _checkValue$1(wantedValue, match, fieldValue) {
|
|
5132
5135
|
match = match ? match.trim() : null;
|
|
@@ -14123,11 +14126,129 @@ const getDivergenceOrEqual = (data, keys) => {
|
|
|
14123
14126
|
return data;
|
|
14124
14127
|
};
|
|
14125
14128
|
|
|
14129
|
+
/**
|
|
14130
|
+
* Utility functions for building admin dashboard configurations
|
|
14131
|
+
* These functions transform simple data arrays into full configuration objects
|
|
14132
|
+
*/
|
|
14133
|
+
|
|
14134
|
+
/**
|
|
14135
|
+
* Format string to kebab-case
|
|
14136
|
+
* @param {string} str - String to format
|
|
14137
|
+
* @returns {string} Kebab-cased string
|
|
14138
|
+
*/
|
|
14139
|
+
function formatToKebabCase(str) {
|
|
14140
|
+
return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
14141
|
+
}
|
|
14142
|
+
|
|
14143
|
+
/**
|
|
14144
|
+
* Build action widgets configuration
|
|
14145
|
+
*
|
|
14146
|
+
* @param {Object} params
|
|
14147
|
+
* @param {Array} params.widgets - Array of widget definitions
|
|
14148
|
+
* @param {Function} params.getRedirectLink - Function to transform paths
|
|
14149
|
+
* @param {Function} params.t - Translation function
|
|
14150
|
+
*
|
|
14151
|
+
* @returns {Array} Configured action widgets
|
|
14152
|
+
*
|
|
14153
|
+
* @example
|
|
14154
|
+
* const widgets = buildActionWidgetsConfig({
|
|
14155
|
+
* widgets: [
|
|
14156
|
+
* { icon: "Search", title: "review-requests", path: "/accounts?pending=true" }
|
|
14157
|
+
* ],
|
|
14158
|
+
* getRedirectLink: (path) => `/app${path}`,
|
|
14159
|
+
* t: (key) => translations[key]
|
|
14160
|
+
* });
|
|
14161
|
+
*/
|
|
14162
|
+
function buildActionWidgetsConfig({
|
|
14163
|
+
widgets = [],
|
|
14164
|
+
getRedirectLink,
|
|
14165
|
+
t
|
|
14166
|
+
}) {
|
|
14167
|
+
return widgets.map(widget => ({
|
|
14168
|
+
icon: widget.icon,
|
|
14169
|
+
title: widget.title,
|
|
14170
|
+
goToPath: getRedirectLink ? getRedirectLink(widget.path) : widget.path,
|
|
14171
|
+
disabled: widget.disabled || false
|
|
14172
|
+
}));
|
|
14173
|
+
}
|
|
14174
|
+
|
|
14175
|
+
/**
|
|
14176
|
+
* Build key indicators configuration
|
|
14177
|
+
*
|
|
14178
|
+
* @param {Object} params
|
|
14179
|
+
* @param {Object} params.keyIndicatorsData - Object with indicator keys and values
|
|
14180
|
+
* @param {Array} params.paths - Array of paths corresponding to each indicator
|
|
14181
|
+
* @param {Function} params.getRedirectLink - Function to transform paths
|
|
14182
|
+
*
|
|
14183
|
+
* @returns {Array} Configured key indicators
|
|
14184
|
+
*
|
|
14185
|
+
* @example
|
|
14186
|
+
* const indicators = buildKeyIndicatorsConfig({
|
|
14187
|
+
* keyIndicatorsData: { totalUsers: 100, totalAccounts: 50 },
|
|
14188
|
+
* paths: ["/users", "/accounts"],
|
|
14189
|
+
* getRedirectLink: (path) => `/app${path}`
|
|
14190
|
+
* });
|
|
14191
|
+
*/
|
|
14192
|
+
function buildKeyIndicatorsConfig({
|
|
14193
|
+
keyIndicatorsData = {},
|
|
14194
|
+
paths = [],
|
|
14195
|
+
getRedirectLink
|
|
14196
|
+
}) {
|
|
14197
|
+
const keys = Object.keys(keyIndicatorsData);
|
|
14198
|
+
return keys.map((key, index) => ({
|
|
14199
|
+
title: formatToKebabCase(key),
|
|
14200
|
+
valueToShow: keyIndicatorsData[key],
|
|
14201
|
+
goToPath: getRedirectLink && paths[index] ? getRedirectLink(paths[index]) : paths[index] || "#"
|
|
14202
|
+
}));
|
|
14203
|
+
}
|
|
14204
|
+
|
|
14205
|
+
/**
|
|
14206
|
+
* Build breadcrumbs configuration
|
|
14207
|
+
*
|
|
14208
|
+
* @param {Object} params
|
|
14209
|
+
* @param {string} params.view - View type (e.g., "accounts", "users")
|
|
14210
|
+
* @param {Function} params.t - Translation function
|
|
14211
|
+
* @param {Function} params.goTo - Navigation function
|
|
14212
|
+
* @param {boolean} params.isView - Is in view mode
|
|
14213
|
+
* @param {boolean} params.isEdit - Is in edit mode
|
|
14214
|
+
* @param {string} params.id - Entity ID
|
|
14215
|
+
* @param {string} params.mode - Current mode
|
|
14216
|
+
* @param {string} params.group - Current group/section
|
|
14217
|
+
* @param {string} params.basePath - Base path for the view
|
|
14218
|
+
*
|
|
14219
|
+
* @returns {Array} Breadcrumb configuration
|
|
14220
|
+
*/
|
|
14221
|
+
function buildBreadcrumbs({
|
|
14222
|
+
view,
|
|
14223
|
+
t,
|
|
14224
|
+
goTo,
|
|
14225
|
+
isView,
|
|
14226
|
+
isEdit,
|
|
14227
|
+
id,
|
|
14228
|
+
mode,
|
|
14229
|
+
group,
|
|
14230
|
+
basePath = "/app"
|
|
14231
|
+
}) {
|
|
14232
|
+
return [{
|
|
14233
|
+
label: t("Admin"),
|
|
14234
|
+
path: basePath
|
|
14235
|
+
}, {
|
|
14236
|
+
label: t(view.charAt(0).toUpperCase() + view.slice(1)),
|
|
14237
|
+
path: `${basePath}/${view}`
|
|
14238
|
+
}, {
|
|
14239
|
+
label: isView ? t("View") : t("Edit"),
|
|
14240
|
+
path: `${basePath}/${view}/${mode}/${id}/${group}`
|
|
14241
|
+
}];
|
|
14242
|
+
}
|
|
14243
|
+
|
|
14126
14244
|
exports.ErrorFormat = ErrorFormat;
|
|
14127
14245
|
exports.MessageTypes = MessageTypes;
|
|
14128
14246
|
exports.StorageManager = StorageManager;
|
|
14129
14247
|
exports.assignParamsToUrl = assignParamsToUrl;
|
|
14130
14248
|
exports.btn = button;
|
|
14249
|
+
exports.buildActionWidgetsConfig = buildActionWidgetsConfig;
|
|
14250
|
+
exports.buildBreadcrumbs = buildBreadcrumbs;
|
|
14251
|
+
exports.buildKeyIndicatorsConfig = buildKeyIndicatorsConfig;
|
|
14131
14252
|
exports.buildQueryString = buildQueryString;
|
|
14132
14253
|
exports.camelCaseToTitle = camelCaseToTitle;
|
|
14133
14254
|
exports.capitalize = capitalize;
|
|
@@ -14155,6 +14276,7 @@ exports.filterSelectOptions = filterSelectOptions;
|
|
|
14155
14276
|
exports.filterString = filterString;
|
|
14156
14277
|
exports.findOptions = findOptions;
|
|
14157
14278
|
exports.formatErrors = formatErrors;
|
|
14279
|
+
exports.formatToKebabCase = formatToKebabCase$1;
|
|
14158
14280
|
exports.getDefaultActiveFilters = getDefaultActiveFilters;
|
|
14159
14281
|
exports.getDivergenceOrEqual = getDivergenceOrEqual;
|
|
14160
14282
|
exports.getImageUploadViewValue = getImageUploadViewValue;
|
package/package.json
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const config = {
|
|
2
|
+
viewBox: "0 0 105 105",
|
|
3
|
+
children: (
|
|
4
|
+
<>
|
|
5
|
+
<svg width="105" height="105" viewBox="0 0 105 105" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
6
|
+
<path d="M26.25 26.25L45.9375 45.9375L26.25 26.25Z" fill="white"/>
|
|
7
|
+
<path d="M13.125 8.75L26.25 13.125V26.25H13.125L8.75 13.125L13.125 8.75Z" fill="white"/>
|
|
8
|
+
<path d="M84.2581 11.9919L72.7622 23.4878C71.0297 25.2203 70.1634 26.0866 69.8388 27.0856C69.5533 27.9642 69.5533 28.9108 69.8388 29.7894C70.1634 30.7884 71.0297 31.6547 72.7622 33.3872L73.8003 34.4253C75.5328 36.1578 76.3991 37.0241 77.398 37.3487C78.2767 37.6342 79.2233 37.6342 80.102 37.3487C81.1009 37.0241 81.9672 36.1578 83.6997 34.4253L94.4531 23.6719C95.6113 26.4901 96.25 29.5767 96.25 32.8125C96.25 46.1019 85.4768 56.875 72.1875 56.875C70.5853 56.875 69.0197 56.7184 67.5052 56.4197C65.3783 56.0003 64.3149 55.7905 63.6702 55.8548C62.9848 55.923 62.647 56.0258 62.0397 56.3508C61.4685 56.6565 60.8955 57.2295 59.7495 58.3755L28.4375 89.6874C24.8131 93.3118 18.9369 93.3118 15.3125 89.6874C11.6881 86.0631 11.6881 80.1868 15.3125 76.5624L46.6245 45.2505C47.7705 44.1045 48.3435 43.5315 48.6492 42.9603C48.9742 42.353 49.077 42.0152 49.1452 41.3298C49.2095 40.6851 48.9997 39.6217 48.5803 37.4948C48.2816 35.9803 48.125 34.4147 48.125 32.8125C48.125 19.5231 58.8981 8.75 72.1875 8.75C76.5866 8.75 80.7099 9.93048 84.2581 11.9919Z" fill="white"/>
|
|
9
|
+
<path d="M52.5002 65.6248L76.5625 89.687C80.1868 93.3114 86.0631 93.3114 89.6874 89.687C93.3118 86.0627 93.3118 80.1864 89.6874 76.562L69.892 56.7669C68.4907 56.6343 67.1243 56.3815 65.8034 56.019C64.1013 55.5518 62.2341 55.8909 60.986 57.139L52.5002 65.6248Z" fill="white"/>
|
|
10
|
+
<path d="M26.25 26.25L45.9375 45.9375M26.25 26.25H13.125L8.75 13.125L13.125 8.75L26.25 13.125V26.25ZM84.2581 11.9919L72.7622 23.4878C71.0297 25.2203 70.1634 26.0866 69.8388 27.0855C69.5533 27.9642 69.5533 28.9108 69.8388 29.7895C70.1634 30.7884 71.0297 31.6547 72.7622 33.3872L73.8003 34.4253C75.5328 36.1578 76.3991 37.0241 77.398 37.3487C78.2767 37.6342 79.2233 37.6342 80.102 37.3487C81.1009 37.0241 81.9672 36.1578 83.6997 34.4253L94.4531 23.6719C95.6113 26.4901 96.25 29.5767 96.25 32.8125C96.25 46.1019 85.4768 56.875 72.1875 56.875C70.5853 56.875 69.0197 56.7184 67.5052 56.4197C65.3783 56.0003 64.3149 55.7905 63.6702 55.8548C62.9848 55.9231 62.647 56.0258 62.0397 56.3508C61.4685 56.6565 60.8955 57.2295 59.7495 58.3755L28.4375 89.6874C24.8131 93.3118 18.9369 93.3118 15.3125 89.6874C11.6881 86.0631 11.6881 80.1868 15.3125 76.5624L46.6245 45.2505C47.7705 44.1045 48.3435 43.5315 48.6492 42.9603C48.9742 42.353 49.0769 42.0152 49.1452 41.3298C49.2095 40.6851 48.9997 39.6217 48.5803 37.4948C48.2816 35.9803 48.125 34.4147 48.125 32.8125C48.125 19.5231 58.8981 8.75 72.1875 8.75C76.5866 8.75 80.7099 9.93048 84.2581 11.9919ZM52.5002 65.6248L76.5625 89.687C80.1868 93.3114 86.0631 93.3114 89.6874 89.687C93.3118 86.0627 93.3118 80.1864 89.6874 76.562L69.892 56.7669C68.4907 56.6343 67.1243 56.3815 65.8034 56.019C64.1013 55.5518 62.2341 55.8909 60.986 57.139L52.5002 65.6248Z" stroke="#AD8A47" stroke-width="3.6" stroke-linecap="round" stroke-linejoin="round"/>
|
|
11
|
+
</svg>
|
|
12
|
+
|
|
13
|
+
</>
|
|
14
|
+
)
|
|
15
|
+
}
|
|
@@ -8,7 +8,7 @@ import AdminAccountsViewScreen from "./AdminScreens/AccountsView.jsx";
|
|
|
8
8
|
* Generate Admin Routes for any application
|
|
9
9
|
*
|
|
10
10
|
* This function returns a complete set of admin routes that can be used directly
|
|
11
|
-
* in
|
|
11
|
+
* in application's route configuration. Just provide the configuration and
|
|
12
12
|
* you get back ready-to-use routes.
|
|
13
13
|
*
|
|
14
14
|
* @param {Object} config - Application-specific configuration
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generic hook for fetching admin dashboard data
|
|
5
|
+
*
|
|
6
|
+
* @param {Object} config - Configuration object
|
|
7
|
+
* @param {Object} config.dashboardService - Service object with getDashboardData method
|
|
8
|
+
* @param {Function} config.onError - Error handler function
|
|
9
|
+
*
|
|
10
|
+
* @returns {Object} Dashboard data and loading states
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* const { data, loading, userGrowthData, fetchUserGrowth } = useAdminDashboard({
|
|
14
|
+
* dashboardService: {
|
|
15
|
+
* getDashboardData: () => DashboardService.getAdminStats(),
|
|
16
|
+
* getUserGrowth: (params) => DashboardService.getUserGrowth(params)
|
|
17
|
+
* },
|
|
18
|
+
* onError: (err) => console.error(err)
|
|
19
|
+
* });
|
|
20
|
+
*/
|
|
21
|
+
export function useAdminDashboard({ dashboardService, onError }) {
|
|
22
|
+
const [data, setData] = useState({});
|
|
23
|
+
const [loading, setLoading] = useState(false);
|
|
24
|
+
const [userGrowthData, setUserGrowthData] = useState([]);
|
|
25
|
+
const [userGrowthDataLoading, setUserGrowthDataLoading] = useState(false);
|
|
26
|
+
|
|
27
|
+
const fetchDashboardData = useCallback(async () => {
|
|
28
|
+
if (!dashboardService?.getDashboardData) {
|
|
29
|
+
console.warn("dashboardService.getDashboardData not provided");
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
setLoading(true);
|
|
34
|
+
try {
|
|
35
|
+
const response = await dashboardService.getDashboardData();
|
|
36
|
+
setData(response || {});
|
|
37
|
+
} catch (err) {
|
|
38
|
+
if (onError) {
|
|
39
|
+
onError(err);
|
|
40
|
+
} else {
|
|
41
|
+
console.error("Error fetching dashboard data:", err);
|
|
42
|
+
}
|
|
43
|
+
} finally {
|
|
44
|
+
setLoading(false);
|
|
45
|
+
}
|
|
46
|
+
}, [dashboardService, onError]);
|
|
47
|
+
|
|
48
|
+
const fetchUserGrowth = useCallback(
|
|
49
|
+
async (params) => {
|
|
50
|
+
if (!dashboardService?.getUserGrowth) {
|
|
51
|
+
console.warn("dashboardService.getUserGrowth not provided");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
setUserGrowthDataLoading(true);
|
|
56
|
+
try {
|
|
57
|
+
const response = await dashboardService.getUserGrowth(params);
|
|
58
|
+
setUserGrowthData(response || []);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
if (onError) {
|
|
61
|
+
onError(err);
|
|
62
|
+
} else {
|
|
63
|
+
console.error("Error fetching user growth:", err);
|
|
64
|
+
}
|
|
65
|
+
} finally {
|
|
66
|
+
setUserGrowthDataLoading(false);
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
[dashboardService, onError]
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
fetchDashboardData();
|
|
74
|
+
}, [fetchDashboardData]);
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
data,
|
|
78
|
+
loading,
|
|
79
|
+
userGrowthData,
|
|
80
|
+
fetchUserGrowth,
|
|
81
|
+
userGrowthDataLoading,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
@@ -9,6 +9,21 @@ class DashboardService extends BaseService {
|
|
|
9
9
|
isApp: true,
|
|
10
10
|
});
|
|
11
11
|
}
|
|
12
|
+
|
|
13
|
+
getUserGrowth(activeFilter) {
|
|
14
|
+
return this.apiGet({
|
|
15
|
+
url: `/accounts/dashboard/user-growth`,
|
|
16
|
+
isUserManager: true,
|
|
17
|
+
params: { activeFilter },
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
getAdminDashboard() {
|
|
22
|
+
return this.apiGet({
|
|
23
|
+
url: `/accounts/dashboard`,
|
|
24
|
+
isUserManager: true,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
12
27
|
}
|
|
13
28
|
|
|
14
29
|
export default createLazyService(DashboardService);
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for building admin dashboard configurations
|
|
3
|
+
* These functions transform simple data arrays into full configuration objects
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Format string to kebab-case
|
|
8
|
+
* @param {string} str - String to format
|
|
9
|
+
* @returns {string} Kebab-cased string
|
|
10
|
+
*/
|
|
11
|
+
export function formatToKebabCase(str) {
|
|
12
|
+
return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Build action widgets configuration
|
|
17
|
+
*
|
|
18
|
+
* @param {Object} params
|
|
19
|
+
* @param {Array} params.widgets - Array of widget definitions
|
|
20
|
+
* @param {Function} params.getRedirectLink - Function to transform paths
|
|
21
|
+
* @param {Function} params.t - Translation function
|
|
22
|
+
*
|
|
23
|
+
* @returns {Array} Configured action widgets
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* const widgets = buildActionWidgetsConfig({
|
|
27
|
+
* widgets: [
|
|
28
|
+
* { icon: "Search", title: "review-requests", path: "/accounts?pending=true" }
|
|
29
|
+
* ],
|
|
30
|
+
* getRedirectLink: (path) => `/app${path}`,
|
|
31
|
+
* t: (key) => translations[key]
|
|
32
|
+
* });
|
|
33
|
+
*/
|
|
34
|
+
export function buildActionWidgetsConfig({ widgets = [], getRedirectLink, t }) {
|
|
35
|
+
return widgets.map((widget) => ({
|
|
36
|
+
icon: widget.icon,
|
|
37
|
+
title: widget.title,
|
|
38
|
+
goToPath: getRedirectLink ? getRedirectLink(widget.path) : widget.path,
|
|
39
|
+
disabled: widget.disabled || false,
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Build key indicators configuration
|
|
45
|
+
*
|
|
46
|
+
* @param {Object} params
|
|
47
|
+
* @param {Object} params.keyIndicatorsData - Object with indicator keys and values
|
|
48
|
+
* @param {Array} params.paths - Array of paths corresponding to each indicator
|
|
49
|
+
* @param {Function} params.getRedirectLink - Function to transform paths
|
|
50
|
+
*
|
|
51
|
+
* @returns {Array} Configured key indicators
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* const indicators = buildKeyIndicatorsConfig({
|
|
55
|
+
* keyIndicatorsData: { totalUsers: 100, totalAccounts: 50 },
|
|
56
|
+
* paths: ["/users", "/accounts"],
|
|
57
|
+
* getRedirectLink: (path) => `/app${path}`
|
|
58
|
+
* });
|
|
59
|
+
*/
|
|
60
|
+
export function buildKeyIndicatorsConfig({ keyIndicatorsData = {}, paths = [], getRedirectLink }) {
|
|
61
|
+
const keys = Object.keys(keyIndicatorsData);
|
|
62
|
+
|
|
63
|
+
return keys.map((key, index) => ({
|
|
64
|
+
title: formatToKebabCase(key),
|
|
65
|
+
valueToShow: keyIndicatorsData[key],
|
|
66
|
+
goToPath: getRedirectLink && paths[index] ? getRedirectLink(paths[index]) : paths[index] || "#",
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Build breadcrumbs configuration
|
|
72
|
+
*
|
|
73
|
+
* @param {Object} params
|
|
74
|
+
* @param {string} params.view - View type (e.g., "accounts", "users")
|
|
75
|
+
* @param {Function} params.t - Translation function
|
|
76
|
+
* @param {Function} params.goTo - Navigation function
|
|
77
|
+
* @param {boolean} params.isView - Is in view mode
|
|
78
|
+
* @param {boolean} params.isEdit - Is in edit mode
|
|
79
|
+
* @param {string} params.id - Entity ID
|
|
80
|
+
* @param {string} params.mode - Current mode
|
|
81
|
+
* @param {string} params.group - Current group/section
|
|
82
|
+
* @param {string} params.basePath - Base path for the view
|
|
83
|
+
*
|
|
84
|
+
* @returns {Array} Breadcrumb configuration
|
|
85
|
+
*/
|
|
86
|
+
export function buildBreadcrumbs({ view, t, goTo, isView, isEdit, id, mode, group, basePath = "/app" }) {
|
|
87
|
+
return [
|
|
88
|
+
{
|
|
89
|
+
label: t("Admin"),
|
|
90
|
+
path: basePath,
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
label: t(view.charAt(0).toUpperCase() + view.slice(1)),
|
|
94
|
+
path: `${basePath}/${view}`,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
label: isView ? t("View") : t("Edit"),
|
|
98
|
+
path: `${basePath}/${view}/${mode}/${id}/${group}`,
|
|
99
|
+
},
|
|
100
|
+
];
|
|
101
|
+
}
|
|
102
|
+
|
package/src/hooks.js
CHANGED
|
@@ -11,4 +11,5 @@ export { useMapOnExpandableWidget } from "./@daf/hooks/useMapOnExpandableWidget"
|
|
|
11
11
|
export { checkPermission, checkPermissionList, usePermissions } from "./@daf/hooks/usePermissions";
|
|
12
12
|
export { useFirebase } from "./@daf/hooks/useFirebase.js"
|
|
13
13
|
export { useIsDatastake } from "./@daf/hooks/useIsDatastake.js"
|
|
14
|
-
export { useWidgetFetch } from "./@daf/hooks/useWidgetFetch.js"
|
|
14
|
+
export { useWidgetFetch } from "./@daf/hooks/useWidgetFetch.js"
|
|
15
|
+
export { useAdminDashboard } from "./@daf/hooks/useAdminDashboard.js"
|
|
@@ -1542,13 +1542,13 @@ ul.ant-menu.ant-menu-dark.ant-menu-root.ant-menu-vertical::-webkit-scrollbar-thu
|
|
|
1542
1542
|
}
|
|
1543
1543
|
|
|
1544
1544
|
.components-layout:not(.nested) .sidenav-sider:not(.custom) .sidenav-cont .menus-cont.not-collapsed .sidemenu-cont .ant-menu.ant-menu-root.ant-menu-inline.ant-menu-dark
|
|
1545
|
-
.ant-menu-item:not(.ant-menu-item-selected) svg path
|
|
1545
|
+
.ant-menu-item:not(.ant-menu-item-selected):not(.ant-menu-item-disabled) svg path
|
|
1546
1546
|
{
|
|
1547
1547
|
stroke: #ffffff !important;
|
|
1548
1548
|
}
|
|
1549
1549
|
|
|
1550
1550
|
.components-layout:not(.nested) .sidenav-sider:not(.custom) .sidenav-cont .menus-cont.not-collapsed .sidemenu-cont .ant-menu.ant-menu-root.ant-menu-inline.ant-menu-dark
|
|
1551
|
-
.ant-menu-item:not(.ant-menu-item-selected) .ant-menu-title-content {
|
|
1551
|
+
.ant-menu-item:not(.ant-menu-item-selected):not(.ant-menu-item-disabled) .ant-menu-title-content {
|
|
1552
1552
|
color: #ffffff !important;
|
|
1553
1553
|
}
|
|
1554
1554
|
|
package/src/utils.js
CHANGED
|
@@ -9,7 +9,7 @@ export { renderTooltip, renderTooltipJsx } from './@daf/utils/tooltip'
|
|
|
9
9
|
export { getRangeOfTicks } from './helpers/chart'
|
|
10
10
|
|
|
11
11
|
export { propHasValue } from './helpers/deepFind'
|
|
12
|
-
export { isEmptyOrSpaces, capitalizeAll, capitalize, camelCaseToTitle, snakeCaseToTitleCase, titleToCamelCase, findOptions, getOptionAsObject, nowToIso, renderTemplateString, renderTemplateStringInObject, truncateString, splitStringInMultipleLines, safeJsonParse, cleanJSON } from './helpers/StringHelper.js'
|
|
12
|
+
export { isEmptyOrSpaces, capitalizeAll, capitalize, camelCaseToTitle, snakeCaseToTitleCase, titleToCamelCase, findOptions, getOptionAsObject, nowToIso, renderTemplateString, renderTemplateStringInObject, truncateString, splitStringInMultipleLines, safeJsonParse, cleanJSON, formatToKebabCase } from './helpers/StringHelper.js'
|
|
13
13
|
export { getNkey, groupSubsections, getImageUploadViewValue } from './@daf/core/components/ViewForm/helper'
|
|
14
14
|
export { renderRows } from './@daf/core/components/Table/helper'
|
|
15
15
|
export { filterOptions, filterString, hasNotChanged, filterSelectOptions, mapSubGroupsInSubmit, mapSubGroupsInGet, filterCreateData, changeInputMeta, renderDateFormatted } from './helpers/Forms'
|
|
@@ -52,4 +52,10 @@ export { createTheme, createAdminTheme } from './helpers/theme.js';
|
|
|
52
52
|
|
|
53
53
|
export { getRedirectLink } from "./@daf/hooks/useIsDatastake.js"
|
|
54
54
|
|
|
55
|
-
export { getSourceString, getDivergenceOrEqual } from './helpers/stringHelper.jsx'
|
|
55
|
+
export { getSourceString, getDivergenceOrEqual } from './helpers/stringHelper.jsx'
|
|
56
|
+
|
|
57
|
+
export {
|
|
58
|
+
buildActionWidgetsConfig,
|
|
59
|
+
buildKeyIndicatorsConfig,
|
|
60
|
+
buildBreadcrumbs
|
|
61
|
+
} from './@daf/utils/adminConfigBuilders.js'
|
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
# Admin Screens - Reusable Admin Panel Components
|
|
2
|
-
|
|
3
|
-
This module provides ready-to-use admin panel screens that can be easily configured for any application.
|
|
4
|
-
|
|
5
|
-
## 🎯 Purpose
|
|
6
|
-
|
|
7
|
-
The admin panel is mostly static across applications - it only needs configuration changes (app name, services, etc.). These components allow you to:
|
|
8
|
-
|
|
9
|
-
1. **Export admin routes from DAF** instead of duplicating code per application
|
|
10
|
-
2. **Configure via props** instead of rewriting components
|
|
11
|
-
3. **Maintain consistency** across all applications using DAF
|
|
12
|
-
|
|
13
|
-
## 📦 Available Components
|
|
14
|
-
|
|
15
|
-
### 1. `AdminDashboardScreen`
|
|
16
|
-
The main admin dashboard with key indicators, action widgets, and statistics.
|
|
17
|
-
|
|
18
|
-
**Configuration:**
|
|
19
|
-
```javascript
|
|
20
|
-
{
|
|
21
|
-
appName: "wazi", // App name for translations
|
|
22
|
-
goTo: useNavigate(), // Navigation function
|
|
23
|
-
t: useTranslation(), // Translation function
|
|
24
|
-
getRedirectLink: (path) => path, // Redirect link generator
|
|
25
|
-
getActionWidgetsConfig: (opts) => [], // Action widgets config
|
|
26
|
-
getKeyIndicatorsConfig: (opts) => [], // Key indicators config
|
|
27
|
-
useWidgetFetch: () => {} // Hook to fetch dashboard data
|
|
28
|
-
}
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
### 2. `AdminUsersScreen`
|
|
32
|
-
User management table with impersonation, password reset, etc.
|
|
33
|
-
|
|
34
|
-
**Configuration:**
|
|
35
|
-
```javascript
|
|
36
|
-
{
|
|
37
|
-
appName: "wazi",
|
|
38
|
-
module: APP, // App identifier
|
|
39
|
-
goTo: useNavigate(),
|
|
40
|
-
t: useTranslation(),
|
|
41
|
-
location: useLocation(),
|
|
42
|
-
isMobile: false,
|
|
43
|
-
userRoles: [], // User roles config
|
|
44
|
-
accountTypes: [], // Account types config
|
|
45
|
-
getRedirectLink: (path) => path,
|
|
46
|
-
AdminService: {}, // Admin service methods
|
|
47
|
-
AuthenticationService: {}, // Auth service methods
|
|
48
|
-
handleError: (err) => {}, // Error handler
|
|
49
|
-
getAppRedirect: ({token}) => "" // App redirect for impersonation
|
|
50
|
-
}
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
### 3. `AdminAccountsScreen`
|
|
54
|
-
Accounts management table with invitation management.
|
|
55
|
-
|
|
56
|
-
**Configuration:**
|
|
57
|
-
```javascript
|
|
58
|
-
{
|
|
59
|
-
appName: "wazi",
|
|
60
|
-
module: APP,
|
|
61
|
-
goTo: useNavigate(),
|
|
62
|
-
t: useTranslation(),
|
|
63
|
-
location: useLocation(),
|
|
64
|
-
isMobile: false,
|
|
65
|
-
accountTypes: [],
|
|
66
|
-
getRedirectLink: (path) => path,
|
|
67
|
-
AdminService: {},
|
|
68
|
-
options: { countries: [] }, // Options with countries, etc.
|
|
69
|
-
useQuery: () => {}, // Query params hook
|
|
70
|
-
NewAccountModal: Component // Optional custom modal
|
|
71
|
-
}
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
### 4. `AdminAccountsViewScreen`
|
|
75
|
-
View/Edit individual accounts with user management.
|
|
76
|
-
|
|
77
|
-
**Configuration:**
|
|
78
|
-
```javascript
|
|
79
|
-
{
|
|
80
|
-
module: APP,
|
|
81
|
-
goTo: useNavigate(),
|
|
82
|
-
t: useTranslation(),
|
|
83
|
-
location: useLocation(),
|
|
84
|
-
getRedirectLink: (path) => path,
|
|
85
|
-
renderBreadCrumbs: (opts) => [], // Breadcrumbs renderer
|
|
86
|
-
id: "account-id", // From route params
|
|
87
|
-
mode: "view|edit", // From route params
|
|
88
|
-
group: "general", // From route params
|
|
89
|
-
userRoles: [],
|
|
90
|
-
options: { countries: [] },
|
|
91
|
-
accountTypes: [],
|
|
92
|
-
accountStatuses: [],
|
|
93
|
-
getAccountData: (opts) => {}, // Fetch account data
|
|
94
|
-
updateAccount: (opts) => {}, // Update account
|
|
95
|
-
handleError: (err) => {},
|
|
96
|
-
toggleAccountStatus: (opts) => {}, // Toggle account status
|
|
97
|
-
transferAdmin: (opts) => {} // Transfer admin rights
|
|
98
|
-
}
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
## 🚀 Usage in Applications
|
|
102
|
-
|
|
103
|
-
### Step 1: Create Configuration Hooks
|
|
104
|
-
|
|
105
|
-
Create a file `privateRoutes/adminConfig.js` in your application:
|
|
106
|
-
|
|
107
|
-
```javascript
|
|
108
|
-
import { useNavigate, useLocation, useParams } from "react-router-dom";
|
|
109
|
-
import { useTranslation } from "react-i18next";
|
|
110
|
-
// ... import your services and hooks
|
|
111
|
-
|
|
112
|
-
export function useAdminDashboardConfig() {
|
|
113
|
-
const goTo = useNavigate();
|
|
114
|
-
const { t } = useTranslation();
|
|
115
|
-
// ... gather all needed config
|
|
116
|
-
|
|
117
|
-
return {
|
|
118
|
-
appName: "your-app",
|
|
119
|
-
goTo,
|
|
120
|
-
t,
|
|
121
|
-
// ... all other config
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Create similar hooks for other screens
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
### Step 2: Create Wrapper Components
|
|
129
|
-
|
|
130
|
-
In your `privateRoutes/index.js`:
|
|
131
|
-
|
|
132
|
-
```javascript
|
|
133
|
-
import {
|
|
134
|
-
AdminDashboardScreen,
|
|
135
|
-
AdminUsersScreen,
|
|
136
|
-
AdminAccountsScreen,
|
|
137
|
-
AdminAccountsViewScreen,
|
|
138
|
-
} from "datastake-daf/dist/components";
|
|
139
|
-
import {
|
|
140
|
-
useAdminDashboardConfig,
|
|
141
|
-
useAdminUsersConfig,
|
|
142
|
-
useAdminAccountsConfig,
|
|
143
|
-
useAdminAccountsViewConfig,
|
|
144
|
-
} from "./adminConfig";
|
|
145
|
-
|
|
146
|
-
function DashboardWrapper() {
|
|
147
|
-
const config = useAdminDashboardConfig();
|
|
148
|
-
return <AdminDashboardScreen config={config} />;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Create similar wrappers for other screens
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
### Step 3: Define Routes
|
|
155
|
-
|
|
156
|
-
```javascript
|
|
157
|
-
const routes = [
|
|
158
|
-
{
|
|
159
|
-
path: "",
|
|
160
|
-
key: "APP_YOUR_APP_DASHBOARD",
|
|
161
|
-
component: <DashboardWrapper />,
|
|
162
|
-
visible: () => true,
|
|
163
|
-
},
|
|
164
|
-
{
|
|
165
|
-
path: "accounts",
|
|
166
|
-
key: "APP_YOUR_APP_ACCOUNTS",
|
|
167
|
-
component: <AccountsWrapper />,
|
|
168
|
-
visible: (user) => userIsAdmin(user),
|
|
169
|
-
},
|
|
170
|
-
// ... more routes
|
|
171
|
-
];
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
## ✅ Benefits
|
|
175
|
-
|
|
176
|
-
1. **No Code Duplication**: Write admin logic once in DAF, use everywhere
|
|
177
|
-
2. **Easy Maintenance**: Fix bugs once, applies to all apps
|
|
178
|
-
3. **Consistent UX**: Same admin experience across all applications
|
|
179
|
-
4. **Configurable**: Each app can customize behavior via config
|
|
180
|
-
5. **Type Safe**: Well-documented configuration interfaces
|
|
181
|
-
|
|
182
|
-
## 🔄 Migration from Old Pattern
|
|
183
|
-
|
|
184
|
-
**Before** (duplicated code per app):
|
|
185
|
-
```
|
|
186
|
-
wazi-frontend/src/screens/Admin/
|
|
187
|
-
├── Dashboard/index.js ❌ Duplicated
|
|
188
|
-
├── Users/index.js ❌ Duplicated
|
|
189
|
-
└── Accounts/index.js ❌ Duplicated
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
**After** (configuration only):
|
|
193
|
-
```
|
|
194
|
-
wazi-frontend/src/privateRoutes/
|
|
195
|
-
├── index.js ✅ Route definitions
|
|
196
|
-
└── adminConfig.js ✅ Configuration hooks
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
## 📚 Example: Complete Setup
|
|
200
|
-
|
|
201
|
-
See `/wazi-frontend/src/privateRoutes/` for a complete working example of how to configure and use these admin screens.
|
|
202
|
-
|
|
203
|
-
## 🔧 Customization
|
|
204
|
-
|
|
205
|
-
If you need custom behavior:
|
|
206
|
-
|
|
207
|
-
1. **Small changes**: Use configuration options
|
|
208
|
-
2. **Moderate changes**: Create a custom wrapper component
|
|
209
|
-
3. **Large changes**: Extend the base admin components
|
|
210
|
-
|
|
211
|
-
## 📝 Notes
|
|
212
|
-
|
|
213
|
-
- All screens expect React Router hooks (useNavigate, useLocation, useParams)
|
|
214
|
-
- Translation keys follow the pattern: `{appName}::key` and `admin::key`
|
|
215
|
-
- Services should follow the AdminService interface from DAF
|
|
216
|
-
|