@appcorp/fusion-storybook 0.2.40 → 0.2.44

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.
Files changed (100) hide show
  1. package/base-modules/admission/constants.d.ts +5 -17
  2. package/base-modules/admission/constants.js +12 -7
  3. package/base-modules/admission/context/use-admission-module.js +11 -48
  4. package/base-modules/admission/filter.js +23 -3
  5. package/base-modules/admission/form.js +49 -19
  6. package/base-modules/attendance/context.js +3 -37
  7. package/base-modules/attendance/filter.js +3 -1
  8. package/base-modules/attendance/form.js +26 -10
  9. package/base-modules/attendance/more-actions.js +34 -25
  10. package/base-modules/campus/context.js +7 -7
  11. package/base-modules/class/cache.js +0 -1
  12. package/base-modules/class/context.js +10 -48
  13. package/base-modules/class/more-actions.js +47 -24
  14. package/base-modules/course/context.js +3 -37
  15. package/base-modules/course/form.js +91 -292
  16. package/base-modules/discount-code/constants.d.ts +5 -0
  17. package/base-modules/discount-code/constants.js +5 -0
  18. package/base-modules/discount-code/context.d.ts +1 -0
  19. package/base-modules/discount-code/context.js +40 -39
  20. package/base-modules/discount-code/form.js +21 -15
  21. package/base-modules/discount-code/more-actions.js +1 -1
  22. package/base-modules/enrollment/context.js +3 -37
  23. package/base-modules/enrollment/form.js +38 -11
  24. package/base-modules/enrollment/more-actions.js +48 -25
  25. package/base-modules/expense/constants.js +1 -1
  26. package/base-modules/expense/context.js +5 -32
  27. package/base-modules/expense/filter.js +50 -3
  28. package/base-modules/expense/form.js +82 -6
  29. package/base-modules/family/context.js +7 -38
  30. package/base-modules/family-member/context.js +7 -39
  31. package/base-modules/fee-structure/context.js +1 -25
  32. package/base-modules/fee-structure/form.js +77 -89
  33. package/base-modules/fee-structure/more-actions.js +45 -22
  34. package/base-modules/rbac/context.d.ts +1 -0
  35. package/base-modules/rbac/context.js +24 -33
  36. package/base-modules/rbac/form.js +3 -1
  37. package/base-modules/school/context.js +1 -1
  38. package/base-modules/school/form.js +34 -14
  39. package/base-modules/section/context.d.ts +1 -0
  40. package/base-modules/section/context.js +40 -47
  41. package/base-modules/section/form.js +25 -80
  42. package/base-modules/section/more-actions.js +47 -24
  43. package/base-modules/section/view.js +9 -7
  44. package/base-modules/student-fee/context/use-student-fee-module.d.ts +1 -0
  45. package/base-modules/student-fee/context/use-student-fee-module.js +48 -32
  46. package/base-modules/student-fee/context.d.ts +1 -1
  47. package/base-modules/student-fee/context.js +1 -1
  48. package/base-modules/student-fee/filter.js +23 -3
  49. package/base-modules/student-fee/form.js +93 -174
  50. package/base-modules/student-fee/view.d.ts +7 -1
  51. package/base-modules/student-fee/view.js +17 -20
  52. package/base-modules/student-profile/constants.d.ts +0 -6
  53. package/base-modules/student-profile/constants.js +1 -3
  54. package/base-modules/student-profile/context/use-student-profile-module.d.ts +1 -0
  55. package/base-modules/student-profile/context/use-student-profile-module.js +62 -55
  56. package/base-modules/student-profile/context.d.ts +1 -1
  57. package/base-modules/student-profile/context.js +1 -1
  58. package/base-modules/student-profile/filter.js +24 -4
  59. package/base-modules/student-profile/form.js +35 -3
  60. package/base-modules/subject/context.d.ts +1 -0
  61. package/base-modules/subject/context.js +38 -47
  62. package/base-modules/subject/more-actions.js +47 -24
  63. package/base-modules/teacher/constants.d.ts +0 -6
  64. package/base-modules/teacher/constants.js +0 -2
  65. package/base-modules/teacher/context.d.ts +1 -0
  66. package/base-modules/teacher/context.js +58 -39
  67. package/base-modules/teacher/form.js +46 -11
  68. package/base-modules/teacher/more-actions.js +45 -22
  69. package/base-modules/user/context/use-user-module.d.ts +1 -0
  70. package/base-modules/user/context/use-user-module.js +36 -32
  71. package/base-modules/user/context.js +1 -1
  72. package/base-modules/user/filter.js +6 -4
  73. package/base-modules/user/form.js +29 -5
  74. package/base-modules/user/more-actions.js +9 -7
  75. package/base-modules/user/view.js +3 -1
  76. package/base-modules/workspace/form.js +18 -8
  77. package/base-modules/workspace-user/context.d.ts +2 -1
  78. package/base-modules/workspace-user/context.js +31 -29
  79. package/package.json +1 -1
  80. package/tsconfig.build.tsbuildinfo +1 -1
  81. package/base-modules/admission/cache.d.ts +0 -14
  82. package/base-modules/admission/cache.js +0 -31
  83. package/base-modules/attendance/cache.d.ts +0 -14
  84. package/base-modules/attendance/cache.js +0 -31
  85. package/base-modules/course/cache.d.ts +0 -14
  86. package/base-modules/course/cache.js +0 -31
  87. package/base-modules/enrollment/cache.d.ts +0 -14
  88. package/base-modules/enrollment/cache.js +0 -31
  89. package/base-modules/expense/cache.d.ts +0 -14
  90. package/base-modules/expense/cache.js +0 -31
  91. package/base-modules/family/cache.d.ts +0 -14
  92. package/base-modules/family/cache.js +0 -31
  93. package/base-modules/family-member/cache.d.ts +0 -14
  94. package/base-modules/family-member/cache.js +0 -31
  95. package/base-modules/rbac/cache.d.ts +0 -27
  96. package/base-modules/rbac/cache.js +0 -46
  97. package/base-modules/student-fee/cache.d.ts +0 -15
  98. package/base-modules/student-fee/cache.js +0 -21
  99. package/base-modules/workspace-user/cache.d.ts +0 -14
  100. package/base-modules/workspace-user/cache.js +0 -31
@@ -16,27 +16,33 @@ import { useDiscountCodeModule } from "./context";
16
16
  import { useTranslations } from "next-intl";
17
17
  import { EnhancedInput } from "@appcorp/shadcn/components/enhanced-input";
18
18
  import { EnhancedTextarea } from "@appcorp/shadcn/components/enhanced-textarea";
19
- import { EnhancedCombobox } from "@appcorp/shadcn/components/enhanced-combobox";
19
+ import { useEnhancedCombobox } from "@appcorp/shadcn/hooks/use-enhanced-combobox";
20
20
  import { EnhancedCheckbox } from "@appcorp/shadcn/components/enhanced-checkbox";
21
- import { Percent, DollarSign } from "lucide-react";
22
21
  import { DISCOUNT_TYPE } from "../../type";
22
+ import { DISCOUNT_CODE_API_ROUTES, DISCOUNT_TYPE_OPTIONS } from "./constants";
23
23
  export const DiscountCodeForm = () => {
24
24
  const { state, handleChange } = useDiscountCodeModule();
25
25
  const t = useTranslations("discountCode");
26
+ const discountTypeLabelMap = {
27
+ PERCENTAGE: t("formOptionPercentage"),
28
+ FIXED: t("formOptionFixed"),
29
+ };
26
30
  const { code, description, discountType, discountValue, enabled, errors } = state;
27
- const discountTypeOptions = [
28
- {
29
- label: t("formDiscountTypeOptionsPercentage"),
30
- value: DISCOUNT_TYPE.PERCENTAGE,
31
- icon: _jsx(Percent, { className: "h-4 w-4" }),
32
- },
33
- {
34
- label: t("formDiscountTypeOptionsFixed"),
35
- value: DISCOUNT_TYPE.FIXED,
36
- icon: _jsx(DollarSign, { className: "h-4 w-4" }),
37
- },
38
- ];
39
- return (_jsx("div", { className: "space-y-6", children: _jsxs("div", { className: "grid grid-cols-1 gap-4", children: [_jsx(EnhancedInput, { error: errors.code, id: "code", info: t("formDiscountCodeInfo"), label: t("formDiscountCodeLabel"), onChange: (e) => handleChange("code", e.target.value.toUpperCase()), placeholder: t("formDiscountCodePlaceholder"), required: true, value: code }), _jsx(EnhancedTextarea, { error: errors.description, id: "description", info: t("formDescriptionInfo"), label: t("formDescriptionLabel"), onChange: (e) => handleChange("description", e.target.value), placeholder: t("formDescriptionPlaceholder"), rows: 3, value: description || "" }), _jsx(EnhancedCombobox, { error: errors.discountType, id: "discountType", info: t("formDiscountTypeInfo"), label: t("formDiscountTypeLabel"), onValueChange: (value) => handleChange("discountType", value), options: discountTypeOptions, placeholder: t("formDiscountTypePlaceholder"), required: true, value: discountType }), _jsx(EnhancedInput, { id: "discountValue", label: t("formDiscountValueLabel"), type: "number", value: (discountValue === null || discountValue === void 0 ? void 0 : discountValue.toString()) || "", onChange: (e) => handleChange("discountValue", parseFloat(e.target.value) || 0), error: errors.discountValue, placeholder: discountType === DISCOUNT_TYPE.PERCENTAGE
31
+ const { enhancedComboboxElement: discountTypeCombo } = useEnhancedCombobox({
32
+ id: "discountType",
33
+ info: t("formDiscountTypeInfo"),
34
+ label: t("formDiscountTypeLabel"),
35
+ onValueChange: (value) => handleChange("discountType", value),
36
+ options: DISCOUNT_TYPE_OPTIONS.map((opt) => ({
37
+ id: opt.value,
38
+ name: discountTypeLabelMap[opt.value] || opt.label,
39
+ })),
40
+ placeholder: t("formDiscountTypePlaceholder"),
41
+ required: true,
42
+ searchEndpoint: DISCOUNT_CODE_API_ROUTES.UNIT,
43
+ value: discountType,
44
+ });
45
+ return (_jsx("div", { className: "space-y-6", children: _jsxs("div", { className: "grid grid-cols-1 gap-4", children: [_jsx(EnhancedInput, { error: errors.code, id: "code", info: t("formDiscountCodeInfo"), label: t("formDiscountCodeLabel"), onChange: (e) => handleChange("code", e.target.value.toUpperCase()), placeholder: t("formDiscountCodePlaceholder"), required: true, value: code }), _jsx(EnhancedTextarea, { error: errors.description, id: "description", info: t("formDescriptionInfo"), label: t("formDescriptionLabel"), onChange: (e) => handleChange("description", e.target.value), placeholder: t("formDescriptionPlaceholder"), rows: 3, value: description || "" }), discountTypeCombo, _jsx(EnhancedInput, { id: "discountValue", label: t("formDiscountValueLabel"), type: "number", value: (discountValue === null || discountValue === void 0 ? void 0 : discountValue.toString()) || "", onChange: (e) => handleChange("discountValue", parseFloat(e.target.value) || 0), error: errors.discountValue, placeholder: discountType === DISCOUNT_TYPE.PERCENTAGE
40
46
  ? t("formDiscountValuePlaceholderPercentage")
41
47
  : t("formDiscountValuePlaceholderFixed"), required: true, min: 0, max: discountType === DISCOUNT_TYPE.PERCENTAGE ? 100 : undefined, step: discountType === DISCOUNT_TYPE.PERCENTAGE ? 0.1 : 0.01, info: discountType === DISCOUNT_TYPE.PERCENTAGE
42
48
  ? t("formDiscountValueInfoPercentage")
@@ -9,5 +9,5 @@ import { useTranslations } from "next-intl";
9
9
  */
10
10
  export const DiscountCodeMoreActions = () => {
11
11
  const t = useTranslations("discountCode");
12
- return (_jsx("div", { className: "space-y-4", children: _jsx("div", { className: "text-sm text-muted-foreground", children: t("actionMoreActions") }) }));
12
+ return (_jsx("div", { className: "space-y-4", children: _jsx("div", { className: "text-sm text-muted-foreground", children: t("moreActionsTitle") }) }));
13
13
  };
@@ -25,7 +25,6 @@ import { useDebounce } from "@react-pakistan/util-functions/hooks/use-debounce";
25
25
  import { createGenericModule } from "@react-pakistan/util-functions/factory/generic-module-factory";
26
26
  import { DRAWER_TYPES } from "@react-pakistan/util-functions/factory/generic-component-factory";
27
27
  import { ENROLLMENT_API_ROUTES, pageLimit } from "./constants";
28
- import { getCachedEnrollments, invalidateEnrollmentsCache } from "./cache";
29
28
  import { enrollmentFormValidation } from "./validate";
30
29
  import { TOAST_VARIANT, generateThemeToast, } from "@appcorp/shadcn/lib/toast-utils";
31
30
  import { getCachedWorkspaceSync } from "../workspace/cache";
@@ -164,7 +163,6 @@ export const useEnrollmentModule = () => {
164
163
  showToast(isCreatedOrUpdated(data)
165
164
  ? t("messagesEnrollmentCreated")
166
165
  : t("messagesEnrollmentUpdated"), TOAST_VARIANT.SUCCESS);
167
- invalidateEnrollmentsCache();
168
166
  (_a = listFetchNowRef.current) === null || _a === void 0 ? void 0 : _a.call(listFetchNowRef);
169
167
  resetFormAndCloseDrawer();
170
168
  }
@@ -189,7 +187,6 @@ export const useEnrollmentModule = () => {
189
187
  }
190
188
  if (data) {
191
189
  showToast(t("messagesEnrollmentDeleted"), TOAST_VARIANT.SUCCESS);
192
- invalidateEnrollmentsCache();
193
190
  (_a = listFetchNowRef.current) === null || _a === void 0 ? void 0 : _a.call(listFetchNowRef);
194
191
  }
195
192
  }, [showToast, t]);
@@ -398,44 +395,13 @@ export const useEnrollmentModule = () => {
398
395
  // ==========================================================================
399
396
  // 1.4.9 EFFECTS
400
397
  // ==========================================================================
401
- // Keep the latest fetch function in a ref so the fetch effect can call it
402
- // without depending on listFetchNow's unstable identity.
403
398
  useEffect(() => {
404
399
  listFetchNowRef.current = listFetchNow;
405
- });
406
- // Initial load via cache; re-fetch directly from API on page/pageLimit/filter/search changes.
400
+ }, [listFetchNow]);
407
401
  useEffect(() => {
408
402
  var _a;
409
- if (!schoolId)
410
- return;
411
- const currentPage = Number(listParams.currentPage) || 1;
412
- const currentPageLimit = Number(listParams.pageLimit) || pageLimit;
413
- const isDefaultLoad = currentPage === 1 &&
414
- currentPageLimit === pageLimit &&
415
- !listParams.searchQuery &&
416
- listParams.filterEnabled === undefined;
417
- if (isDefaultLoad) {
418
- (async () => {
419
- try {
420
- const { count, items } = await getCachedEnrollments({
421
- params: listParams,
422
- });
423
- dispatch({
424
- type: ENROLLMENT_ACTION_TYPES.SET_ITEMS,
425
- payload: { items: items || [], count: count || 0 },
426
- });
427
- }
428
- catch (_a) {
429
- showToast(t("messagesNetworkError"), TOAST_VARIANT.ERROR);
430
- }
431
- })();
432
- }
433
- else {
434
- // Bypass cache for pagination, pageLimit, filter and search changes.
435
- // Use ref to avoid the infinite-loop caused by listFetchNow's unstable identity.
436
- (_a = listFetchNowRef.current) === null || _a === void 0 ? void 0 : _a.call(listFetchNowRef);
437
- }
438
- }, [listParams, dispatch, showToast, schoolId, t]);
403
+ (_a = listFetchNowRef.current) === null || _a === void 0 ? void 0 : _a.call(listFetchNowRef);
404
+ }, [listParams]);
439
405
  // ==========================================================================
440
406
  // 1.4.10 RETURN
441
407
  // ==========================================================================
@@ -11,12 +11,13 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
11
11
  */
12
12
  import { EnhancedInput } from "@appcorp/shadcn/components/enhanced-input";
13
13
  import { EnhancedCheckbox } from "@appcorp/shadcn/components/enhanced-checkbox";
14
- import { EnhancedCombobox } from "@appcorp/shadcn/components/enhanced-combobox";
14
+ import { useEnhancedCombobox } from "@appcorp/shadcn/hooks/use-enhanced-combobox";
15
15
  import { Separator } from "@appcorp/shadcn/components/ui/separator";
16
16
  import { DATE_FORMATS, formatDate } from "@react-pakistan/util-functions";
17
17
  import { useEnrollmentModule } from "./context";
18
18
  import { getCachedStudentProfilesSync } from "../student-profile/cache";
19
19
  import { getCachedSectionsSync } from "../section/cache";
20
+ import { ENROLLMENT_API_ROUTES } from "./constants";
20
21
  import { useTranslations } from "next-intl";
21
22
  // ============================================================================
22
23
  // COMPONENT
@@ -24,16 +25,42 @@ import { useTranslations } from "next-intl";
24
25
  export const EnrollmentForm = () => {
25
26
  const { state: { enabled, enrollmentDate, errors, sectionId, studentProfileId, }, handleChange, } = useEnrollmentModule();
26
27
  const t = useTranslations("enrollment");
28
+ const { enhancedComboboxElement: studentProfileIdCombo } = useEnhancedCombobox({
29
+ emptyText: t("formNoStudentEmpty"),
30
+ id: "studentProfileId",
31
+ info: t("formStudentInfo"),
32
+ label: t("formStudentLabel"),
33
+ onValueChange: (value) => handleChange("studentProfileId", value),
34
+ options: (getCachedStudentProfilesSync().items || []).map((s) => ({
35
+ id: s.id,
36
+ name: s.studentCode || s.id,
37
+ })),
38
+ placeholder: t("formStudentPlaceholder"),
39
+ required: true,
40
+ searchEndpoint: ENROLLMENT_API_ROUTES.UNIT,
41
+ searchPlaceholder: t("formSearchStudentsPlaceholder"),
42
+ value: studentProfileId || "",
43
+ });
44
+ const { enhancedComboboxElement: sectionIdCombo } = useEnhancedCombobox({
45
+ emptyText: t("formNoSectionEmpty"),
46
+ id: "sectionId",
47
+ info: t("sectionInfo"),
48
+ label: t("section"),
49
+ onValueChange: (value) => handleChange("sectionId", value),
50
+ options: (getCachedSectionsSync().items || []).map((s) => {
51
+ var _a;
52
+ return ({
53
+ id: s.id,
54
+ name: ((_a = s.class) === null || _a === void 0 ? void 0 : _a.code) ? `${s.class.code}-${s.name}` : s.name,
55
+ });
56
+ }),
57
+ placeholder: t("sectionPlaceholder"),
58
+ required: true,
59
+ searchEndpoint: ENROLLMENT_API_ROUTES.UNIT,
60
+ searchPlaceholder: t("formSearchSectionsPlaceholder"),
61
+ value: sectionId || "",
62
+ });
27
63
  return (_jsxs("div", { className: "space-y-4", children: [_jsx(EnhancedInput, { error: errors.enrollmentDate, id: "enrollmentDate", info: t("formEnrollmentDateInfo"), label: t("formEnrollmentDateLabel"), onChange: (e) => handleChange("enrollmentDate", e.target.value), placeholder: t("formEnrollmentDatePlaceholder"), required: true, type: "date", value: enrollmentDate
28
64
  ? formatDate(enrollmentDate, DATE_FORMATS.YYYY_MM_DD)
29
- : "" }), _jsx(Separator, {}), _jsx(EnhancedCombobox, { emptyText: t("formNoStudentEmpty"), error: errors.studentProfileId, id: "studentProfileId", info: t("formStudentInfo"), label: t("formStudentLabel"), onValueChange: (value) => handleChange("studentProfileId", value), options: (getCachedStudentProfilesSync().items || []).map((s) => ({
30
- label: s.studentCode || s.id,
31
- value: s.id,
32
- })), placeholder: t("formStudentPlaceholder"), required: true, searchPlaceholder: t("formSearchStudentsPlaceholder"), value: studentProfileId || "" }), _jsx(EnhancedCombobox, { emptyText: t("formNoSectionEmpty"), error: errors.sectionId, id: "sectionId", info: t("sectionInfo"), label: t("section"), onValueChange: (value) => handleChange("sectionId", value), options: (getCachedSectionsSync().items || []).map((s) => {
33
- var _a;
34
- return ({
35
- label: ((_a = s.class) === null || _a === void 0 ? void 0 : _a.code) ? `${s.class.code}-${s.name}` : s.name,
36
- value: s.id,
37
- });
38
- }), placeholder: t("sectionPlaceholder"), required: true, searchPlaceholder: t("formSearchSectionsPlaceholder"), value: sectionId || "" }), _jsx(EnhancedCheckbox, { checked: enabled, error: errors.enabled, id: "enabled", info: t("actionToggleEnableOrDisableEnrollment"), label: t("formActiveEnrollmentLabel"), onCheckedChange: (checked) => handleChange("enabled", checked) })] }));
65
+ : "" }), _jsx(Separator, {}), studentProfileIdCombo, sectionIdCombo, _jsx(EnhancedCheckbox, { checked: enabled, error: errors.enabled, id: "enabled", info: t("actionToggleEnableOrDisableEnrollment"), label: t("formActiveEnrollmentLabel"), onCheckedChange: (checked) => handleChange("enabled", checked) })] }));
39
66
  };
@@ -7,7 +7,6 @@ import converter from "json-2-csv";
7
7
  import { Timeline } from "../../components/timeline";
8
8
  import { useTranslations } from "next-intl";
9
9
  import { ENROLLMENT_API_ROUTES, pageLimit } from "./constants";
10
- import { invalidateEnrollmentsCache } from "./cache";
11
10
  import { ENROLLMENT_ACTION_TYPES, useEnrollmentContext } from "./context";
12
11
  import { useRef, useEffect, useCallback } from "react";
13
12
  const workspace = getCachedWorkspaceSync();
@@ -68,13 +67,15 @@ async function pollBulkJob(jobId, signal, onProgress) {
68
67
  }
69
68
  }
70
69
  }
71
- function formatErrorSummary(errors) {
70
+ function formatErrorSummary(t, errors) {
72
71
  if (!(errors === null || errors === void 0 ? void 0 : errors.length))
73
72
  return "";
74
- const lines = errors.slice(0, 5).map((e) => `Row ${e.row}: ${e.error}`);
73
+ const lines = errors
74
+ .slice(0, 5)
75
+ .map((e) => t("messagesBulkRowError", { row: e.row, error: e.error }));
75
76
  const remaining = errors.length - 5;
76
77
  if (remaining > 0)
77
- lines.push(`...and ${remaining} more`);
78
+ lines.push(t("messagesBulkMoreRows", { count: remaining }));
78
79
  return lines.join("\n");
79
80
  }
80
81
  export const EnrollmentMoreActions = () => {
@@ -107,7 +108,7 @@ export const EnrollmentMoreActions = () => {
107
108
  const text = await file.text();
108
109
  const records = converter.csv2json(text);
109
110
  if (!Array.isArray(records) || records.length === 0) {
110
- showErrorToast("CSV file is empty or invalid");
111
+ showErrorToast(t("messagesBulkCsvEmpty"));
111
112
  return;
112
113
  }
113
114
  // Client-side validation — basic required field check
@@ -117,15 +118,15 @@ export const EnrollmentMoreActions = () => {
117
118
  const msgs = [];
118
119
  if (method === "POST") {
119
120
  if (!((_b = row.studentProfileId) === null || _b === void 0 ? void 0 : _b.trim()))
120
- msgs.push("studentProfileId is required");
121
+ msgs.push(t("validationRequiredStudentProfileId"));
121
122
  if (!((_c = row.sectionId) === null || _c === void 0 ? void 0 : _c.trim()))
122
- msgs.push("sectionId is required");
123
+ msgs.push(t("validationRequiredSectionId"));
123
124
  if (!((_d = row.enrollmentDate) === null || _d === void 0 ? void 0 : _d.trim()))
124
- msgs.push("enrollmentDate is required");
125
+ msgs.push(t("validationRequiredEnrollmentDate"));
125
126
  }
126
127
  else {
127
128
  if (!((_e = row.id) === null || _e === void 0 ? void 0 : _e.trim()))
128
- msgs.push("id is required for update");
129
+ msgs.push(t("validationRequiredIdForUpdate"));
129
130
  }
130
131
  if (msgs.length > 0) {
131
132
  validationErrors.push({ row: i + 1, messages: msgs });
@@ -134,40 +135,59 @@ export const EnrollmentMoreActions = () => {
134
135
  if (validationErrors.length > 0) {
135
136
  const summary = validationErrors
136
137
  .slice(0, 5)
137
- .map((e) => `Row ${e.row}: ${e.messages.join("; ")}`)
138
+ .map((e) => t("messagesBulkRowError", {
139
+ row: e.row,
140
+ error: e.messages.join("; "),
141
+ }))
138
142
  .join("\n");
139
- const remaining = validationErrors.length - 5;
140
- showErrorToast(`Validation failed for ${validationErrors.length} row(s).\n${summary}${remaining > 0 ? `\n...and ${remaining} more` : ""}`);
143
+ showErrorToast(t("messagesBulkValidationFailed", {
144
+ count: validationErrors.length,
145
+ errors: summary,
146
+ }));
141
147
  return;
142
148
  }
143
149
  try {
144
- showInfoToast(`Bulk ${label} job queued (${records.length} records). Processing...`);
150
+ showInfoToast(t("messagesBulkJobQueued", { action: label, count: records.length }));
145
151
  let jobId;
146
152
  try {
147
153
  jobId = await submitBulkJob(text, method, signal);
148
154
  }
149
155
  catch (submitError) {
150
- showErrorToast(`Failed to submit ${label} job: ${submitError instanceof Error ? submitError.message : "Unknown error"}`);
156
+ showErrorToast(t("messagesBulkJobSubmitFailed", {
157
+ action: label,
158
+ error: submitError instanceof Error
159
+ ? submitError.message
160
+ : t("unknownError"),
161
+ }));
151
162
  return;
152
163
  }
153
164
  const status = await pollBulkJob(jobId, signal, (processed, total) => {
154
- showInfoToast(`Processing ${processed}/${total} enrollments...`);
165
+ showInfoToast(t("messagesBulkProgress", { processed, total }));
155
166
  });
156
167
  if (signal.aborted)
157
168
  return;
158
169
  if (status.status === "completed") {
159
170
  const r = status.results;
160
171
  if (r && ((_f = r.errors) === null || _f === void 0 ? void 0 : _f.length) > 0) {
161
- const summary = formatErrorSummary(r.errors);
162
- showSuccessToast(`Created ${r.created} | Updated ${r.updated} | Skipped ${r.skipped}\n${summary}`);
172
+ const summary = formatErrorSummary(t, r.errors);
173
+ showSuccessToast(t("messagesBulkResults", {
174
+ created: r.created,
175
+ updated: r.updated,
176
+ skipped: r.skipped,
177
+ }) +
178
+ "\n" +
179
+ summary);
163
180
  }
164
181
  else if (r) {
165
- showSuccessToast(`Created ${r.created} | Updated ${r.updated} | Skipped ${r.skipped}`);
182
+ showSuccessToast(t("messagesBulkResults", {
183
+ created: r.created,
184
+ updated: r.updated,
185
+ skipped: r.skipped,
186
+ }));
166
187
  }
167
188
  else {
168
- showSuccessToast("Bulk operation completed successfully");
189
+ showSuccessToast(t("messagesBulkSuccess"));
169
190
  }
170
- invalidateEnrollmentsCache();
171
191
  const schoolId = ((_g = workspace === null || workspace === void 0 ? void 0 : workspace.school) === null || _g === void 0 ? void 0 : _g.id) || "";
172
192
  fetch(`${ENROLLMENT_API_ROUTES.LIST}?currentPage=1&pageLimit=${pageLimit}&schoolId=${schoolId}`, {
173
193
  headers: {
@@ -193,17 +213,20 @@ export const EnrollmentMoreActions = () => {
193
213
  else {
194
214
  const r = status.results;
195
215
  const detail = ((_h = r === null || r === void 0 ? void 0 : r.errors) === null || _h === void 0 ? void 0 : _h.length)
196
- ? formatErrorSummary(r.errors)
197
- : "Unknown error";
198
- showErrorToast(`Bulk ${label} failed.\n${detail}`);
216
+ ? formatErrorSummary(t, r.errors)
217
+ : t("unknownError");
218
+ showErrorToast(t("messagesBulkFailed", { action: label }) + "\n" + detail);
199
219
  }
200
220
  }
201
221
  catch (error) {
202
222
  if (error.message === "Polling cancelled")
203
223
  return;
204
- showErrorToast(`Bulk ${label} failed: ${error instanceof Error ? error.message : "Unknown error"}`);
224
+ showErrorToast(t("messagesBulkFailedDetail", {
225
+ action: label,
226
+ error: error instanceof Error ? error.message : t("unknownError"),
227
+ }));
205
228
  }
206
- }, [dispatch]);
229
+ }, [dispatch, t]);
207
230
  const handleBulkCreate = useCallback((files) => handleBulkFlow(files, "POST"), [handleBulkFlow]);
208
231
  const handleBulkUpdate = useCallback((files) => handleBulkFlow(files, "PUT"), [handleBulkFlow]);
209
232
  const create = [
@@ -51,6 +51,6 @@ export const PAYMENT_METHOD_OPTIONS = [
51
51
  { label: "Debit Card", value: "DEBIT_CARD" },
52
52
  { label: "Cheque", value: "CHEQUE" },
53
53
  { label: "Online", value: "ONLINE" },
54
- { label: "UPI", value: "UPI" },
54
+ // { label: "UPI", value: "UPI" },
55
55
  { label: "Mobile Wallet", value: "MOBILE_WALLET" },
56
56
  ];
@@ -28,10 +28,9 @@ import { createGenericModule } from "@react-pakistan/util-functions/factory/gene
28
28
  import { DRAWER_TYPES } from "@react-pakistan/util-functions/factory/generic-component-factory";
29
29
  import { generateThemeToast, TOAST_VARIANT, } from "@appcorp/shadcn/lib/toast-utils";
30
30
  import { PAYMENT_METHOD, } from "../../type";
31
- import { getCachedExpenses, invalidateExpensesCache } from "./cache";
32
- import { expenseFormValidation } from "./validate";
33
31
  import { EXPENSE_API_ROUTES, pageLimit } from "./constants";
34
32
  import { getCachedWorkspaceSync } from "../workspace/cache";
33
+ import { expenseFormValidation } from "./validate";
35
34
  // ============================================================================
36
35
  // 1.1 DRAWER TYPES
37
36
  // ============================================================================
@@ -155,12 +154,6 @@ export const useExpenseModule = () => {
155
154
  }), [state, schoolId]);
156
155
  const byIdParams = useMemo(() => ({ id: state.id }), [state.id]);
157
156
  const deleteParams = useMemo(() => ({ id: state.id }), [state.id]);
158
- const isDefaultListState = state.currentPage === 1 &&
159
- state.pageLimit === pageLimit &&
160
- !debouncedQuery &&
161
- state.filterEnabled === undefined &&
162
- !state.filterCategory &&
163
- !state.filterStatus;
164
157
  // ============================================================================
165
158
  // 1.4.3 UTILITIES
166
159
  // ============================================================================
@@ -213,7 +206,6 @@ export const useExpenseModule = () => {
213
206
  }
214
207
  if (data) {
215
208
  const isCreated = isCreatedOrUpdated(data);
216
- invalidateExpensesCache();
217
209
  showToast(isCreated ? t("messagesExpenseCreated") : t("messagesExpenseUpdated"), TOAST_VARIANT.SUCCESS);
218
210
  resetFormAndCloseDrawer();
219
211
  (_a = listFetchNowRef.current) === null || _a === void 0 ? void 0 : _a.call(listFetchNowRef);
@@ -241,7 +233,6 @@ export const useExpenseModule = () => {
241
233
  return;
242
234
  }
243
235
  if (data) {
244
- invalidateExpensesCache();
245
236
  showToast(t("messagesExpenseDeleted"), TOAST_VARIANT.SUCCESS);
246
237
  (_a = listFetchNowRef.current) === null || _a === void 0 ? void 0 : _a.call(listFetchNowRef);
247
238
  }
@@ -454,33 +445,15 @@ export const useExpenseModule = () => {
454
445
  // ============================================================================
455
446
  // 1.4.9 EFFECTS
456
447
  // ============================================================================
448
+ useEffect(() => {
449
+ listFetchNowRef.current = listFetchNow;
450
+ }, [listFetchNow]);
457
451
  useEffect(() => {
458
452
  var _a;
459
453
  if (!schoolId)
460
454
  return;
461
- if (isDefaultListState) {
462
- (async () => {
463
- try {
464
- const { count, items } = await getCachedExpenses({
465
- params: listParams,
466
- });
467
- dispatch({
468
- type: EXPENSE_ACTION_TYPES.SET_ITEMS,
469
- payload: { items: items || [], count: count || 0 },
470
- });
471
- }
472
- catch (_a) {
473
- showToast(t("messagesNetworkError"), TOAST_VARIANT.ERROR);
474
- }
475
- })();
476
- return;
477
- }
478
455
  (_a = listFetchNowRef.current) === null || _a === void 0 ? void 0 : _a.call(listFetchNowRef);
479
- }, [dispatch, isDefaultListState, listParams, schoolId, showToast, t]);
480
- // Sync ref to always point at latest listFetchNow (avoids stale closure in callbacks)
481
- useEffect(() => {
482
- listFetchNowRef.current = listFetchNow;
483
- }, [listFetchNow]);
456
+ }, [listParams, schoolId]);
484
457
  // ============================================================================
485
458
  // 1.4.10 RETURN
486
459
  // ============================================================================
@@ -7,20 +7,67 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
7
  * filterCategory (combobox), and filterStatus (combobox).
8
8
  */
9
9
  import { EnhancedRadio } from "@appcorp/shadcn/components/enhanced-radio";
10
- import { EnhancedCombobox } from "@appcorp/shadcn/components/enhanced-combobox";
10
+ import { useEnhancedCombobox } from "@appcorp/shadcn/hooks/use-enhanced-combobox";
11
11
  import { useExpenseModule } from "./context";
12
- import { EXPENSE_CATEGORY_OPTIONS, EXPENSE_STATUS_OPTIONS } from "./constants";
12
+ import { EXPENSE_CATEGORY_OPTIONS, EXPENSE_STATUS_OPTIONS, EXPENSE_API_ROUTES } from "./constants";
13
13
  import { useTranslations } from "next-intl";
14
14
  export const ExpenseFilter = () => {
15
15
  const { state, handleChange } = useExpenseModule();
16
16
  const { filterCategory, filterEnabled, filterStatus } = state;
17
17
  const t = useTranslations("expense");
18
+ const categoryLabelMap = {
19
+ SALARY: t("formOptionSalary"),
20
+ UTILITIES: t("formOptionUtilities"),
21
+ MAINTENANCE: t("formOptionMaintenance"),
22
+ SUPPLIES: t("formOptionSupplies"),
23
+ TRANSPORT: t("formOptionTransport"),
24
+ MARKETING: t("formOptionMarketing"),
25
+ INSURANCE: t("formOptionInsurance"),
26
+ RENT: t("formOptionRent"),
27
+ TECHNOLOGY: t("formOptionTechnology"),
28
+ PROFESSIONAL: t("formOptionProfessional"),
29
+ TRAINING: t("formOptionTraining"),
30
+ EVENTS: t("formOptionEvents"),
31
+ FOOD: t("formOptionFood"),
32
+ MISCELLANEOUS: t("formOptionMiscellaneous"),
33
+ };
34
+ const statusLabelMap = {
35
+ PENDING: t("formOptionPending"),
36
+ APPROVED: t("formOptionApproved"),
37
+ PAID: t("formOptionPaid"),
38
+ REJECTED: t("formOptionRejected"),
39
+ CANCELLED: t("formOptionCancelled"),
40
+ };
18
41
  const filterEnabledValue = filterEnabled === undefined
19
42
  ? "undefined"
20
43
  : filterEnabled
21
44
  ? "true"
22
45
  : "false";
23
- return (_jsx("div", { className: "space-y-4", children: _jsxs("div", { className: "grid grid-cols-1 gap-4", children: [_jsx(EnhancedCombobox, { emptyText: t("filterNoCategoryFoundLabel"), id: "filterCategory", info: t("filterByCategory"), label: t("filterCategoryLabel"), onValueChange: (value) => handleChange("filterCategory", value), options: EXPENSE_CATEGORY_OPTIONS, placeholder: t("filterAllCategoriesLabel"), searchPlaceholder: t("filterSearchCategoriesLabel"), value: filterCategory || "" }), _jsx(EnhancedCombobox, { emptyText: t("filterNoStatusFoundLabel"), id: "filterStatus", info: t("filterByStatus"), label: t("filterOptionStatus"), onValueChange: (value) => handleChange("filterStatus", value), options: EXPENSE_STATUS_OPTIONS, placeholder: t("filterAllStatusesLabel"), searchPlaceholder: t("filterSearchStatusLabel"), value: filterStatus || "" }), _jsx(EnhancedRadio, { label: t("filterOptionEnabled"), name: "filterEnabled", value: filterEnabledValue, options: [
46
+ const { enhancedComboboxElement: filterCategoryCombo } = useEnhancedCombobox({
47
+ emptyText: t("filterNoCategoryFoundLabel"),
48
+ id: "filterCategory",
49
+ info: t("filterByCategory"),
50
+ label: t("filterCategoryLabel"),
51
+ onValueChange: (value) => handleChange("filterCategory", value),
52
+ options: EXPENSE_CATEGORY_OPTIONS.map((opt) => ({ id: opt.value, name: categoryLabelMap[opt.value] || opt.label })),
53
+ placeholder: t("filterAllCategoriesLabel"),
54
+ searchEndpoint: EXPENSE_API_ROUTES.UNIT,
55
+ searchPlaceholder: t("filterSearchCategoriesLabel"),
56
+ value: filterCategory || "",
57
+ });
58
+ const { enhancedComboboxElement: filterStatusCombo } = useEnhancedCombobox({
59
+ emptyText: t("filterNoStatusFoundLabel"),
60
+ id: "filterStatus",
61
+ info: t("filterByStatus"),
62
+ label: t("filterOptionStatus"),
63
+ onValueChange: (value) => handleChange("filterStatus", value),
64
+ options: EXPENSE_STATUS_OPTIONS.map((opt) => ({ id: opt.value, name: statusLabelMap[opt.value] || opt.label })),
65
+ placeholder: t("filterAllStatusesLabel"),
66
+ searchEndpoint: EXPENSE_API_ROUTES.UNIT,
67
+ searchPlaceholder: t("filterSearchStatusLabel"),
68
+ value: filterStatus || "",
69
+ });
70
+ return (_jsx("div", { className: "space-y-4", children: _jsxs("div", { className: "grid grid-cols-1 gap-4", children: [filterCategoryCombo, filterStatusCombo, _jsx(EnhancedRadio, { label: t("filterOptionEnabled"), name: "filterEnabled", value: filterEnabledValue, options: [
24
71
  { label: t("filterOptionAll"), value: "undefined" },
25
72
  { label: t("filterOptionEnabled"), value: "true" },
26
73
  { label: t("filterOptionDisabled"), value: "false" },