@appcorp/fusion-storybook 0.1.4
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/README.md +147 -0
- package/base-modules/admission/cache.d.ts +14 -0
- package/base-modules/admission/cache.js +31 -0
- package/base-modules/admission/constants.d.ts +32 -0
- package/base-modules/admission/constants.js +37 -0
- package/base-modules/admission/context.d.ts +369 -0
- package/base-modules/admission/context.js +755 -0
- package/base-modules/admission/filter.d.ts +1 -0
- package/base-modules/admission/filter.js +31 -0
- package/base-modules/admission/form.d.ts +1 -0
- package/base-modules/admission/form.js +86 -0
- package/base-modules/admission/more-actions.d.ts +1 -0
- package/base-modules/admission/more-actions.js +5 -0
- package/base-modules/admission/page.d.ts +16 -0
- package/base-modules/admission/page.js +128 -0
- package/base-modules/admission/validate.d.ts +38 -0
- package/base-modules/admission/validate.js +70 -0
- package/base-modules/admission/view.d.ts +1 -0
- package/base-modules/admission/view.js +40 -0
- package/base-modules/discount-code/cache.d.ts +27 -0
- package/base-modules/discount-code/cache.js +46 -0
- package/base-modules/discount-code/constants.d.ts +19 -0
- package/base-modules/discount-code/constants.js +26 -0
- package/base-modules/discount-code/context.d.ts +154 -0
- package/base-modules/discount-code/context.js +416 -0
- package/base-modules/discount-code/filter.d.ts +9 -0
- package/base-modules/discount-code/filter.js +14 -0
- package/base-modules/discount-code/form.d.ts +1 -0
- package/base-modules/discount-code/form.js +44 -0
- package/base-modules/discount-code/more-actions.d.ts +9 -0
- package/base-modules/discount-code/more-actions.js +13 -0
- package/base-modules/discount-code/page.d.ts +16 -0
- package/base-modules/discount-code/page.js +120 -0
- package/base-modules/discount-code/validate.d.ts +19 -0
- package/base-modules/discount-code/validate.js +38 -0
- package/base-modules/discount-code/view.d.ts +1 -0
- package/base-modules/discount-code/view.js +26 -0
- package/base-modules/family/cache.d.ts +14 -0
- package/base-modules/family/cache.js +31 -0
- package/base-modules/family/constants.d.ts +22 -0
- package/base-modules/family/constants.js +26 -0
- package/base-modules/family/context.d.ts +173 -0
- package/base-modules/family/context.js +441 -0
- package/base-modules/family/filter.d.ts +1 -0
- package/base-modules/family/filter.js +28 -0
- package/base-modules/family/form.d.ts +1 -0
- package/base-modules/family/form.js +12 -0
- package/base-modules/family/more-actions.d.ts +1 -0
- package/base-modules/family/more-actions.js +5 -0
- package/base-modules/family/page.d.ts +16 -0
- package/base-modules/family/page.js +120 -0
- package/base-modules/family/validate.d.ts +15 -0
- package/base-modules/family/validate.js +18 -0
- package/base-modules/family/view.d.ts +1 -0
- package/base-modules/family/view.js +18 -0
- package/base-modules/family-member/cache.d.ts +14 -0
- package/base-modules/family-member/cache.js +31 -0
- package/base-modules/family-member/constants.d.ts +22 -0
- package/base-modules/family-member/constants.js +26 -0
- package/base-modules/family-member/context.d.ts +215 -0
- package/base-modules/family-member/context.js +486 -0
- package/base-modules/family-member/filter.d.ts +1 -0
- package/base-modules/family-member/filter.js +28 -0
- package/base-modules/family-member/form.d.ts +1 -0
- package/base-modules/family-member/form.js +17 -0
- package/base-modules/family-member/more-actions.d.ts +1 -0
- package/base-modules/family-member/more-actions.js +5 -0
- package/base-modules/family-member/page.d.ts +16 -0
- package/base-modules/family-member/page.js +122 -0
- package/base-modules/family-member/validate.d.ts +24 -0
- package/base-modules/family-member/validate.js +27 -0
- package/base-modules/family-member/view.d.ts +1 -0
- package/base-modules/family-member/view.js +40 -0
- package/base-modules/student-profile/cache.d.ts +14 -0
- package/base-modules/student-profile/cache.js +31 -0
- package/base-modules/student-profile/constants.d.ts +105 -0
- package/base-modules/student-profile/constants.js +132 -0
- package/base-modules/student-profile/context.d.ts +290 -0
- package/base-modules/student-profile/context.js +583 -0
- package/base-modules/student-profile/filter.d.ts +1 -0
- package/base-modules/student-profile/filter.js +26 -0
- package/base-modules/student-profile/form.d.ts +1 -0
- package/base-modules/student-profile/form.js +19 -0
- package/base-modules/student-profile/more-actions.d.ts +1 -0
- package/base-modules/student-profile/more-actions.js +5 -0
- package/base-modules/student-profile/page.d.ts +9 -0
- package/base-modules/student-profile/page.js +86 -0
- package/base-modules/student-profile/validate.d.ts +23 -0
- package/base-modules/student-profile/validate.js +34 -0
- package/base-modules/student-profile/view.d.ts +1 -0
- package/base-modules/student-profile/view.js +29 -0
- package/base-modules/workspace/cache.d.ts +9 -0
- package/base-modules/workspace/cache.js +28 -0
- package/base-modules/workspace/constants.d.ts +10 -0
- package/base-modules/workspace/constants.js +18 -0
- package/base-modules/workspace/context.d.ts +187 -0
- package/base-modules/workspace/context.js +293 -0
- package/base-modules/workspace/drawer.d.ts +1 -0
- package/base-modules/workspace/drawer.js +24 -0
- package/base-modules/workspace/filter.d.ts +1 -0
- package/base-modules/workspace/filter.js +13 -0
- package/base-modules/workspace/form.d.ts +1 -0
- package/base-modules/workspace/form.js +40 -0
- package/base-modules/workspace/more-actions.d.ts +1 -0
- package/base-modules/workspace/more-actions.js +13 -0
- package/base-modules/workspace/page.d.ts +1 -0
- package/base-modules/workspace/page.js +31 -0
- package/base-modules/workspace/validate.d.ts +9 -0
- package/base-modules/workspace/validate.js +8 -0
- package/base-modules/workspace/view.d.ts +1 -0
- package/base-modules/workspace/view.js +52 -0
- package/components/rbac-no-access.d.ts +6 -0
- package/components/rbac-no-access.js +4 -0
- package/constants.d.ts +81 -0
- package/constants.js +81 -0
- package/lib/utils.d.ts +2 -0
- package/lib/utils.js +5 -0
- package/package.json +104 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/type.d.ts +1141 -0
- package/type.js +427 -0
- package/utils/admission-pdf.d.ts +78 -0
- package/utils/admission-pdf.js +73 -0
- package/utils/clear-cache.d.ts +1 -0
- package/utils/clear-cache.js +8 -0
- package/utils/format-value.d.ts +1 -0
- package/utils/format-value.js +1 -0
- package/utils/pdf-generator.d.ts +41 -0
- package/utils/pdf-generator.js +107 -0
- package/utils/resolve-rbac-permissions.d.ts +11 -0
- package/utils/resolve-rbac-permissions.js +251 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const AdmissionFilter: () => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* Admission Filter Component
|
|
5
|
+
*
|
|
6
|
+
* Filter UI for admission records. Uses the module state pattern:
|
|
7
|
+
* `const { state, handleChange, handleSetStartDate, handleSetEndDate } = useAdmissionModule()`
|
|
8
|
+
*/
|
|
9
|
+
import { EnhancedCombobox } from "@appcorp/shadcn/components/enhanced-combobox";
|
|
10
|
+
import { EnhancedRadio } from "@appcorp/shadcn/components/enhanced-radio";
|
|
11
|
+
import { useTranslations } from "next-intl";
|
|
12
|
+
import { ADMISSION_STATUS_OPTIONS } from "./constants";
|
|
13
|
+
import { useAdmissionModule } from "./context";
|
|
14
|
+
export const AdmissionFilter = () => {
|
|
15
|
+
const { state, handleChange, handleSetStartDate, handleSetEndDate } = useAdmissionModule();
|
|
16
|
+
const { filterEnabled, filterAdmissionStatus, filterStartDate, filterEndDate, } = state;
|
|
17
|
+
const t = useTranslations("admission");
|
|
18
|
+
const filterEnabledValue = filterEnabled === undefined
|
|
19
|
+
? "undefined"
|
|
20
|
+
: filterEnabled
|
|
21
|
+
? "true"
|
|
22
|
+
: "false";
|
|
23
|
+
return (_jsx("div", { className: "space-y-4", children: _jsxs("div", { className: "grid grid-cols-1 gap-4", children: [_jsx(EnhancedCombobox, { id: "filterAdmissionStatus", label: t("admissionStatus"), info: t("filterByAdmissionStatus"), options: [...ADMISSION_STATUS_OPTIONS], value: filterAdmissionStatus !== null && filterAdmissionStatus !== void 0 ? filterAdmissionStatus : undefined, onValueChange: (value) => handleChange("filterAdmissionStatus", value) }), _jsxs("div", { children: [_jsx("label", { htmlFor: "startDate", className: "text-foreground mb-1 block text-sm font-medium", children: t("startDate") }), _jsx("input", { id: "startDate", type: "date", value: filterStartDate || "", onChange: (e) => handleSetStartDate(e.target.value), className: "border-input bg-background text-foreground focus:ring-ring w-full rounded-md border px-3 py-2 focus:border-transparent focus:ring-2 focus:outline-none" })] }), _jsxs("div", { children: [_jsx("label", { htmlFor: "endDate", className: "text-foreground mb-1 block text-sm font-medium", children: t("endDate") }), _jsx("input", { id: "endDate", type: "date", value: filterEndDate || "", onChange: (e) => handleSetEndDate(e.target.value), className: "border-input bg-background text-foreground focus:ring-ring w-full rounded-md border px-3 py-2 focus:border-transparent focus:ring-2 focus:outline-none" })] }), _jsx(EnhancedRadio, { label: t("enabled"), name: "filterEnabled", value: filterEnabledValue, options: [
|
|
24
|
+
{ label: t("all"), value: "undefined" },
|
|
25
|
+
{ label: t("enabled"), value: "true" },
|
|
26
|
+
{ label: t("disabled"), value: "false" },
|
|
27
|
+
], onValueChange: (next) => {
|
|
28
|
+
const parsed = next === "true" ? true : next === "false" ? false : undefined;
|
|
29
|
+
handleChange("filterEnabled", parsed);
|
|
30
|
+
} })] }) }));
|
|
31
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const AdmissionForm: () => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* Admission Form Component
|
|
5
|
+
*
|
|
6
|
+
* Create/edit form for admission records. Uses the module state pattern:
|
|
7
|
+
* `const { state, handleChange, dispatch } = useAdmissionModule()`
|
|
8
|
+
*
|
|
9
|
+
* Note: the `state` field in `moduleState` (province/state) is aliased
|
|
10
|
+
* to `provinceState` to avoid a name collision with the hook's `state` object.
|
|
11
|
+
*/
|
|
12
|
+
import { EnhancedInput } from "@appcorp/shadcn/components/enhanced-input";
|
|
13
|
+
import { EnhancedTextarea } from "@appcorp/shadcn/components/enhanced-textarea";
|
|
14
|
+
import { EnhancedCheckbox } from "@appcorp/shadcn/components/enhanced-checkbox";
|
|
15
|
+
import { EnhancedCombobox } from "@appcorp/shadcn/components/enhanced-combobox";
|
|
16
|
+
import { useAdmissionModule, ADMISSION_ACTION_TYPES } from "./context";
|
|
17
|
+
import { Separator } from "@appcorp/shadcn/components/ui/separator";
|
|
18
|
+
import { GENDER } from "../../type";
|
|
19
|
+
import { useEffect } from "react";
|
|
20
|
+
import { getCachedWorkspaceSync } from "../workspace/cache";
|
|
21
|
+
import { getDialCode, VALUE_DELIMITER } from "@react-pakistan/util-functions";
|
|
22
|
+
import { v4 as uuidv4 } from "uuid";
|
|
23
|
+
import { getCachedDiscountCodesSync } from "../discount-code/cache";
|
|
24
|
+
import { useTranslations } from "next-intl";
|
|
25
|
+
export const AdmissionForm = () => {
|
|
26
|
+
const { state, handleChange, dispatch } = useAdmissionModule();
|
|
27
|
+
const { address, admissionNotes, bForm, city, classForAdmission, country, discountCode, dob, emergencyContact, errors, fatherCnic, fatherFirstName, fatherLastName, fatherMobile, fatherOccupation, fatherOrganization, firstName, gender, id, lastName, motherCnic, motherFirstName, motherLastName, motherMobile, notes, previousSchool, registrationCode, siblings, state: provinceState, } = state;
|
|
28
|
+
const t = useTranslations("admission");
|
|
29
|
+
const workspace = getCachedWorkspaceSync();
|
|
30
|
+
const discountCodes = getCachedDiscountCodesSync();
|
|
31
|
+
const discountCodeOptions = (discountCodes.items || [])
|
|
32
|
+
.filter((code) => code.enabled)
|
|
33
|
+
.map((code) => {
|
|
34
|
+
var _a;
|
|
35
|
+
return ({
|
|
36
|
+
label: `${code.code} - ${code.discountValue}${code.discountType === "PERCENTAGE" ? "%" : (_a = workspace === null || workspace === void 0 ? void 0 : workspace.school) === null || _a === void 0 ? void 0 : _a.currency}`,
|
|
37
|
+
value: code.code,
|
|
38
|
+
description: code.description || undefined,
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
var _a, _b, _c, _d, _e, _f;
|
|
43
|
+
if (!id) {
|
|
44
|
+
dispatch({
|
|
45
|
+
type: ADMISSION_ACTION_TYPES.SET_INPUT_FIELD,
|
|
46
|
+
payload: { key: "city", value: ((_a = workspace === null || workspace === void 0 ? void 0 : workspace.school) === null || _a === void 0 ? void 0 : _a.city) || "" },
|
|
47
|
+
});
|
|
48
|
+
dispatch({
|
|
49
|
+
type: ADMISSION_ACTION_TYPES.SET_INPUT_FIELD,
|
|
50
|
+
payload: { key: "state", value: ((_b = workspace === null || workspace === void 0 ? void 0 : workspace.school) === null || _b === void 0 ? void 0 : _b.state) || "" },
|
|
51
|
+
});
|
|
52
|
+
dispatch({
|
|
53
|
+
type: ADMISSION_ACTION_TYPES.SET_INPUT_FIELD,
|
|
54
|
+
payload: { key: "country", value: ((_c = workspace === null || workspace === void 0 ? void 0 : workspace.school) === null || _c === void 0 ? void 0 : _c.country) || "" },
|
|
55
|
+
});
|
|
56
|
+
dispatch({
|
|
57
|
+
type: ADMISSION_ACTION_TYPES.SET_INPUT_FIELD,
|
|
58
|
+
payload: {
|
|
59
|
+
key: "fatherMobile",
|
|
60
|
+
value: getDialCode(((_d = workspace === null || workspace === void 0 ? void 0 : workspace.school) === null || _d === void 0 ? void 0 : _d.country) || "PK") || "",
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
dispatch({
|
|
64
|
+
type: ADMISSION_ACTION_TYPES.SET_INPUT_FIELD,
|
|
65
|
+
payload: {
|
|
66
|
+
key: "motherMobile",
|
|
67
|
+
value: getDialCode(((_e = workspace === null || workspace === void 0 ? void 0 : workspace.school) === null || _e === void 0 ? void 0 : _e.country) || "PK") || "",
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
dispatch({
|
|
71
|
+
type: ADMISSION_ACTION_TYPES.SET_INPUT_FIELD,
|
|
72
|
+
payload: {
|
|
73
|
+
key: "registrationCode",
|
|
74
|
+
value: `${(_f = workspace === null || workspace === void 0 ? void 0 : workspace.school) === null || _f === void 0 ? void 0 : _f.code}${VALUE_DELIMITER.COLON}${uuidv4()
|
|
75
|
+
.slice(0, 8)
|
|
76
|
+
.toUpperCase()}`,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}, [id]);
|
|
81
|
+
return (_jsxs("div", { className: "grid grid-cols-1 gap-2 space-y-2", children: [_jsx("h4", { className: "m-0 p-0", children: t("studentInformation") }), _jsxs("div", { className: "grid grid-cols-5 gap-4", children: [_jsx(EnhancedInput, { error: errors.firstName, id: "firstName", info: t("firstNameInfo"), label: t("firstName"), onChange: (e) => handleChange("firstName", e.target.value), placeholder: t("enterFirstName"), required: true, type: "text", value: firstName || "" }), _jsx(EnhancedInput, { error: errors.lastName, id: "lastName", info: t("lastNameInfo"), label: t("lastName"), onChange: (e) => handleChange("lastName", e.target.value), placeholder: t("enterLastName"), required: true, type: "text", value: lastName || "" }), _jsx(EnhancedInput, { error: errors.bForm, id: "bForm", info: t("bFormInfo"), label: t("bForm"), onChange: (e) => handleChange("bForm", e.target.value), placeholder: t("enterBForm"), required: true, type: "text", value: bForm || "" }), _jsx(EnhancedCombobox, { emptyText: t("noGenderOptionsFound"), error: errors.gender, id: "gender", info: t("genderInfo"), label: t("gender"), required: true, onValueChange: (value) => handleChange("gender", value), options: [
|
|
82
|
+
{ label: t("male"), value: GENDER.MALE },
|
|
83
|
+
{ label: t("female"), value: GENDER.FEMALE },
|
|
84
|
+
{ label: t("other"), value: GENDER.OTHER },
|
|
85
|
+
], placeholder: t("selectGender"), searchPlaceholder: t("searchGender"), value: gender }), _jsx(EnhancedInput, { error: errors.dob, id: "dob", info: t("dobInfo"), label: t("dob"), onChange: (e) => handleChange("dob", e.target.value), placeholder: t("selectDob"), required: true, type: "date", value: dob || "" }), _jsx(EnhancedInput, { error: errors.registrationCode, id: "registrationCode", info: t("registrationCodeInfo"), label: t("registrationCode"), onChange: (e) => handleChange("registrationCode", e.target.value), placeholder: t("enterRegistrationCode"), required: true, type: "text", value: registrationCode || "", disabled: true }), _jsx(EnhancedCombobox, { emptyText: t("noDiscountCodesAvailable"), error: errors.discountCode, id: "discountCode", info: t("discountCodeInfo"), label: t("discountCode"), onValueChange: (value) => handleChange("discountCode", value), options: discountCodeOptions, placeholder: t("enterDiscountCode"), searchPlaceholder: t("searchDiscountCodes"), value: discountCode || "" }), _jsx(EnhancedCheckbox, { label: t("hafiz"), id: "hafiz", info: t("hafizInfo"), onCheckedChange: (checked) => handleChange("hafiz", checked) }), _jsx(EnhancedCheckbox, { label: t("orphan"), id: "orphan", info: t("orphanInfo"), onCheckedChange: (checked) => handleChange("orphan", checked) })] }), _jsx(Separator, {}), _jsx("h4", { className: "m-0 p-0", children: t("fatherInformation") }), _jsxs("div", { className: "grid grid-cols-6 gap-4", children: [_jsx(EnhancedInput, { error: errors.fatherFirstName, id: "fatherFirstName", info: t("fatherFirstNameInfo"), label: t("firstName"), onChange: (e) => handleChange("fatherFirstName", e.target.value), placeholder: t("enterFirstName"), required: true, type: "text", value: fatherFirstName || "" }), _jsx(EnhancedInput, { error: errors.fatherLastName, id: "fatherLastName", info: t("fatherLastNameInfo"), label: t("lastName"), onChange: (e) => handleChange("fatherLastName", e.target.value), placeholder: t("enterLastName"), required: true, type: "text", value: fatherLastName || "" }), _jsx(EnhancedInput, { error: errors.fatherCnic, id: "fatherCnic", info: t("fatherCnicInfo"), label: t("cnic"), onChange: (e) => handleChange("fatherCnic", e.target.value), placeholder: t("enterBForm"), required: true, type: "text", value: fatherCnic || "" }), _jsx(EnhancedInput, { error: errors.fatherMobile, id: "fatherMobile", info: t("fatherMobileInfo"), label: t("mobile"), onChange: (e) => handleChange("fatherMobile", e.target.value), placeholder: "+923001234567", required: true, type: "text", value: fatherMobile || "" }), _jsx(EnhancedInput, { error: errors.fatherOccupation, id: "fatherOccupation", info: t("fatherOccupationInfo"), label: t("occupation"), onChange: (e) => handleChange("fatherOccupation", e.target.value), placeholder: t("enterOccupation"), required: false, type: "text", value: fatherOccupation || "" }), _jsx(EnhancedInput, { error: errors.fatherOrganization, id: "fatherOrganization", info: t("fatherOrganizationInfo"), label: t("organization"), onChange: (e) => handleChange("fatherOrganization", e.target.value), placeholder: t("enterOrganization"), required: false, type: "text", value: fatherOrganization || "" }), _jsx(EnhancedCheckbox, { label: t("emergencyContact"), id: "emergencyContactFather", info: t("fatherEmergencyContactInfo"), checked: emergencyContact === "Father", onCheckedChange: (checked) => handleChange("emergencyContact", checked ? "Father" : "") })] }), _jsx(Separator, {}), _jsx("h4", { className: "m-0 p-0", children: t("motherInformation") }), _jsxs("div", { className: "grid grid-cols-6 gap-4", children: [_jsx(EnhancedInput, { error: errors.motherFirstName, id: "motherFirstName", info: t("motherFirstNameInfo"), label: t("firstName"), onChange: (e) => handleChange("motherFirstName", e.target.value), placeholder: t("enterFirstName"), required: true, type: "text", value: motherFirstName || "" }), _jsx(EnhancedInput, { error: errors.motherLastName, id: "motherLastName", info: t("motherLastNameInfo"), label: t("lastName"), onChange: (e) => handleChange("motherLastName", e.target.value), placeholder: t("enterLastName"), required: true, type: "text", value: motherLastName || "" }), _jsx(EnhancedInput, { error: errors.motherCnic, id: "motherCnic", info: t("motherCnicInfo"), label: t("cnic"), onChange: (e) => handleChange("motherCnic", e.target.value), placeholder: t("enterBForm"), required: true, type: "text", value: motherCnic || "" }), _jsx(EnhancedInput, { error: errors.motherMobile, id: "motherMobile", info: t("motherMobileInfo"), label: t("mobile"), onChange: (e) => handleChange("motherMobile", e.target.value), placeholder: "+923001234567", required: true, type: "text", value: motherMobile || "" }), _jsx(EnhancedCheckbox, { label: t("emergencyContact"), id: "emergencyContactMother", info: t("motherEmergencyContactInfo"), checked: emergencyContact === "Mother", onCheckedChange: (checked) => handleChange("emergencyContact", checked ? "Mother" : "") })] }), _jsx(Separator, {}), _jsx("h4", { className: "m-0 p-0", children: t("homeInformation") }), _jsxs("div", { className: "grid grid-cols-6 gap-4", children: [_jsx(EnhancedTextarea, { error: errors.address, id: "homeAddress", info: t("homeAddressInfo"), label: t("homeAddress"), onChange: (e) => handleChange("address", e.target.value), placeholder: t("enterHomeAddress"), required: true, rows: 2, value: address || "" }), _jsx(EnhancedInput, { error: errors.city, id: "city", info: t("cityInfo"), label: t("city"), onChange: (e) => handleChange("city", e.target.value), placeholder: t("enterCity"), required: true, type: "text", value: city || "" }), _jsx(EnhancedInput, { error: errors.state, id: "state", info: t("stateInfo"), label: t("state"), onChange: (e) => handleChange("state", e.target.value), placeholder: t("enterState"), required: true, type: "text", value: provinceState || "" }), _jsx(EnhancedInput, { error: errors.country, id: "country", info: t("countryInfo"), label: t("country"), onChange: (e) => handleChange("country", e.target.value), placeholder: t("enterCountry"), required: true, type: "text", value: country || "" })] }), _jsx(Separator, {}), _jsx("h4", { className: "m-0 p-0", children: t("admissionInformation") }), _jsxs("div", { className: "grid grid-cols-6 gap-4", children: [_jsx(EnhancedInput, { error: errors.classForAdmission, id: "classForAdmission", info: t("classForAdmissionInfo"), label: t("classForAdmission"), onChange: (e) => handleChange("classForAdmission", e.target.value), placeholder: t("enterClassForAdmission"), required: true, type: "text", value: classForAdmission || "" }), _jsx(EnhancedInput, { error: errors.previousSchool, id: "previousSchool", info: t("previousSchoolInfo"), label: t("previousSchool"), onChange: (e) => handleChange("previousSchool", e.target.value), placeholder: t("enterPreviousSchool"), type: "text", value: previousSchool || "" }), _jsx(EnhancedTextarea, { error: errors.siblings, id: "siblings", info: t("siblingsInfo"), label: t("siblings"), onChange: (e) => handleChange("siblings", e.target.value), placeholder: t("enterSiblings"), rows: 3, value: siblings || "" })] }), _jsx(Separator, {}), _jsx("h4", { className: "m-0 p-0", children: t("officeUse") }), _jsxs("div", { className: "grid grid-cols-4 gap-4", children: [_jsx(EnhancedTextarea, { error: errors.notes, id: "notes", info: t("notesInfo"), label: t("notes"), onChange: (e) => handleChange("notes", e.target.value), placeholder: t("enterNotes"), rows: 3, value: notes || "" }), _jsx(EnhancedTextarea, { error: errors.admissionNotes, id: "admissionNotes", info: t("admissionNotesInfo"), label: t("admissionNotes"), onChange: (e) => handleChange("admissionNotes", e.target.value), placeholder: t("enterAdmissionNotes"), rows: 3, value: admissionNotes || "" })] })] }));
|
|
86
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const AdmissionMoreActions: () => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admission Page Component
|
|
3
|
+
*
|
|
4
|
+
* Thin wrapper around GenericModulePage. All handlers, header actions, and row
|
|
5
|
+
* actions live in context.tsx — this file only owns:
|
|
6
|
+
* - tableBodyCols (static, module-level constant)
|
|
7
|
+
* - admissionConfig (memoised on locale change only; size fixed to "full" so
|
|
8
|
+
* the component type produced by createGenericModulePage never changes at
|
|
9
|
+
* runtime, preventing the infinite-remount re-render loop)
|
|
10
|
+
* - permission guard
|
|
11
|
+
*/
|
|
12
|
+
import { FC } from "react";
|
|
13
|
+
import { USER_ROLE } from "../../type";
|
|
14
|
+
export declare const AdmissionPage: FC<{
|
|
15
|
+
userRole: USER_ROLE;
|
|
16
|
+
}>;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* Admission Page Component
|
|
5
|
+
*
|
|
6
|
+
* Thin wrapper around GenericModulePage. All handlers, header actions, and row
|
|
7
|
+
* actions live in context.tsx — this file only owns:
|
|
8
|
+
* - tableBodyCols (static, module-level constant)
|
|
9
|
+
* - admissionConfig (memoised on locale change only; size fixed to "full" so
|
|
10
|
+
* the component type produced by createGenericModulePage never changes at
|
|
11
|
+
* runtime, preventing the infinite-remount re-render loop)
|
|
12
|
+
* - permission guard
|
|
13
|
+
*/
|
|
14
|
+
import { useMemo } from "react";
|
|
15
|
+
import { COMPONENT_TYPE } from "@appcorp/shadcn/components/enhanced-table";
|
|
16
|
+
import { createGenericModulePage, } from "@react-pakistan/util-functions/factory/generic-component-factory";
|
|
17
|
+
import { useAdmissionModule, AdmissionProvider, ADMISSION_DRAWER, ADMISSION_ACTION_TYPES, } from "./context";
|
|
18
|
+
import { AdmissionFilter } from "./filter";
|
|
19
|
+
import { AdmissionForm } from "./form";
|
|
20
|
+
import { AdmissionView } from "./view";
|
|
21
|
+
import { resolveRbacPermissions } from "../../utils/resolve-rbac-permissions";
|
|
22
|
+
import { RbacNoAccess } from "../../components/rbac-no-access";
|
|
23
|
+
import { useTranslations } from "next-intl";
|
|
24
|
+
import { AdmissionMoreActions } from "./more-actions";
|
|
25
|
+
// ============================================================================
|
|
26
|
+
// TABLE COLUMN CONFIGURATION (static — no runtime deps)
|
|
27
|
+
// ============================================================================
|
|
28
|
+
const tableBodyCols = [
|
|
29
|
+
{ componentType: COMPONENT_TYPE.ID, key: "id" },
|
|
30
|
+
{ componentType: COMPONENT_TYPE.OBJECT, key: ["studentDetails:firstName"] },
|
|
31
|
+
{ componentType: COMPONENT_TYPE.OBJECT, key: ["studentDetails:lastName"] },
|
|
32
|
+
{
|
|
33
|
+
componentType: COMPONENT_TYPE.OBJECT,
|
|
34
|
+
key: ["studentDetails:registrationCode"],
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
componentType: COMPONENT_TYPE.OBJECT,
|
|
38
|
+
key: ["admissionDetails:classForAdmission"],
|
|
39
|
+
},
|
|
40
|
+
{ componentType: COMPONENT_TYPE.TEXT, key: "status" },
|
|
41
|
+
{ componentType: COMPONENT_TYPE.BOOLEAN, key: "enabled" },
|
|
42
|
+
{ componentType: COMPONENT_TYPE.ACTIONS },
|
|
43
|
+
];
|
|
44
|
+
const tableColumns = [
|
|
45
|
+
{ label: "id", width: "5%" },
|
|
46
|
+
{ label: "firstName", width: "15%" },
|
|
47
|
+
{ label: "lastName", width: "15%" },
|
|
48
|
+
{ label: "registrationCode", width: "20%" },
|
|
49
|
+
{ label: "class", width: "20%" },
|
|
50
|
+
{ label: "status", width: "10%" },
|
|
51
|
+
{ label: "enabled", width: "10%" },
|
|
52
|
+
{ label: "actions", width: "5%" },
|
|
53
|
+
];
|
|
54
|
+
// ============================================================================
|
|
55
|
+
// TRANSLATED COLUMNS HELPER
|
|
56
|
+
// ============================================================================
|
|
57
|
+
const getTranslatedColumns = (t) => tableColumns.map((col) => (Object.assign(Object.assign({}, col), { label: t(col.label) })));
|
|
58
|
+
// ============================================================================
|
|
59
|
+
// COMPONENT FACTORY (creates JSX elements when config is created, not during render)
|
|
60
|
+
// ============================================================================
|
|
61
|
+
const createComponentInstances = () => ({
|
|
62
|
+
filter: _jsx(AdmissionFilter, {}),
|
|
63
|
+
form: _jsx(AdmissionForm, {}),
|
|
64
|
+
moreActions: _jsx(AdmissionMoreActions, {}),
|
|
65
|
+
view: _jsx(AdmissionView, {}),
|
|
66
|
+
});
|
|
67
|
+
// ============================================================================
|
|
68
|
+
// CONFIG CREATION HELPER
|
|
69
|
+
// ============================================================================
|
|
70
|
+
const createAdmissionConfig = (t, drawer, dispatch) => {
|
|
71
|
+
const components = createComponentInstances();
|
|
72
|
+
return {
|
|
73
|
+
moduleName: "admission",
|
|
74
|
+
tableColumns: getTranslatedColumns(t),
|
|
75
|
+
cancelLabel: t("cancel"),
|
|
76
|
+
drawerTitle: t("admission"),
|
|
77
|
+
filterContent: components.filter,
|
|
78
|
+
formContent: components.form,
|
|
79
|
+
moreActionsContent: components.moreActions,
|
|
80
|
+
saveLabel: t("save"),
|
|
81
|
+
searchPlaceholder: t("searchAdmissions"),
|
|
82
|
+
tableDescription: t("manageAdmissionsInTheSystem"),
|
|
83
|
+
tableTitle: t("admission"),
|
|
84
|
+
viewContent: components.view,
|
|
85
|
+
size: drawer === ADMISSION_DRAWER.FORM_DRAWER ? "full" : "small",
|
|
86
|
+
onClearFilters: () => {
|
|
87
|
+
dispatch({ type: ADMISSION_ACTION_TYPES.RESET_FORM });
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
// ============================================================================
|
|
92
|
+
// STABLE PAGE COMPONENT (created once, outside render)
|
|
93
|
+
// ============================================================================
|
|
94
|
+
const GenericAdmissionPage = createGenericModulePage({
|
|
95
|
+
moduleName: "admission",
|
|
96
|
+
tableColumns: [],
|
|
97
|
+
cancelLabel: "",
|
|
98
|
+
drawerTitle: "",
|
|
99
|
+
filterContent: _jsx(AdmissionFilter, {}),
|
|
100
|
+
formContent: _jsx(AdmissionForm, {}),
|
|
101
|
+
moreActionsContent: _jsx(AdmissionMoreActions, {}),
|
|
102
|
+
saveLabel: "",
|
|
103
|
+
searchPlaceholder: "",
|
|
104
|
+
tableDescription: "",
|
|
105
|
+
tableTitle: "",
|
|
106
|
+
viewContent: _jsx(AdmissionView, {}),
|
|
107
|
+
size: "small",
|
|
108
|
+
onClearFilters: () => { },
|
|
109
|
+
});
|
|
110
|
+
// ============================================================================
|
|
111
|
+
// INNER PAGE (requires AdmissionProvider context)
|
|
112
|
+
// ============================================================================
|
|
113
|
+
const AdmissionPageInner = ({ userRole }) => {
|
|
114
|
+
const t = useTranslations("admission");
|
|
115
|
+
const context = useAdmissionModule();
|
|
116
|
+
// Memoize permission check to avoid recalculation on every render
|
|
117
|
+
const hasPermission = useMemo(() => resolveRbacPermissions({
|
|
118
|
+
userRole,
|
|
119
|
+
moduleName: "Admission",
|
|
120
|
+
}), [userRole]);
|
|
121
|
+
// Memoize config creation
|
|
122
|
+
const admissionConfig = useMemo(() => createAdmissionConfig(t, context.state.drawer, context.dispatch), [t, context.state.drawer, context.dispatch]);
|
|
123
|
+
if (!hasPermission) {
|
|
124
|
+
return _jsx(RbacNoAccess, { moduleName: "Admission" });
|
|
125
|
+
}
|
|
126
|
+
return (_jsx("div", { className: "p-4", children: _jsx(GenericAdmissionPage, { config: admissionConfig, context: context, tableBodyCols: tableBodyCols }) }));
|
|
127
|
+
};
|
|
128
|
+
export const AdmissionPage = ({ userRole }) => (_jsx(AdmissionProvider, { children: _jsx(AdmissionPageInner, { userRole: userRole }) }));
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admission Validation Schema
|
|
3
|
+
*
|
|
4
|
+
* Zod validation schema for admission form with i18n keys
|
|
5
|
+
*/
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { GENDER } from "../../type";
|
|
8
|
+
export declare const admissionFormValidation: z.ZodObject<{
|
|
9
|
+
registrationCode: z.ZodString;
|
|
10
|
+
firstName: z.ZodString;
|
|
11
|
+
lastName: z.ZodString;
|
|
12
|
+
bForm: z.ZodString;
|
|
13
|
+
dob: z.ZodString;
|
|
14
|
+
gender: z.ZodEnum<typeof GENDER>;
|
|
15
|
+
discountCode: z.ZodOptional<z.ZodString>;
|
|
16
|
+
hafiz: z.ZodOptional<z.ZodBoolean>;
|
|
17
|
+
orphan: z.ZodOptional<z.ZodBoolean>;
|
|
18
|
+
fatherFirstName: z.ZodString;
|
|
19
|
+
fatherLastName: z.ZodString;
|
|
20
|
+
fatherCnic: z.ZodString;
|
|
21
|
+
fatherMobile: z.ZodString;
|
|
22
|
+
fatherOccupation: z.ZodOptional<z.ZodString>;
|
|
23
|
+
fatherOrganization: z.ZodOptional<z.ZodString>;
|
|
24
|
+
motherFirstName: z.ZodString;
|
|
25
|
+
motherLastName: z.ZodString;
|
|
26
|
+
motherCnic: z.ZodString;
|
|
27
|
+
motherMobile: z.ZodString;
|
|
28
|
+
address: z.ZodString;
|
|
29
|
+
city: z.ZodString;
|
|
30
|
+
state: z.ZodString;
|
|
31
|
+
country: z.ZodString;
|
|
32
|
+
classForAdmission: z.ZodString;
|
|
33
|
+
previousSchool: z.ZodOptional<z.ZodString>;
|
|
34
|
+
siblings: z.ZodOptional<z.ZodString>;
|
|
35
|
+
notes: z.ZodOptional<z.ZodString>;
|
|
36
|
+
admissionNotes: z.ZodOptional<z.ZodString>;
|
|
37
|
+
emergencyContact: z.ZodOptional<z.ZodString>;
|
|
38
|
+
}, z.core.$strip>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admission Validation Schema
|
|
3
|
+
*
|
|
4
|
+
* Zod validation schema for admission form with i18n keys
|
|
5
|
+
*/
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { GENDER } from "../../type";
|
|
8
|
+
export const admissionFormValidation = z.object({
|
|
9
|
+
// Student Information
|
|
10
|
+
registrationCode: z.string().min(1, "Registration code is required"),
|
|
11
|
+
firstName: z.string().min(1, "First name is required"),
|
|
12
|
+
lastName: z.string().min(1, "Last name is required"),
|
|
13
|
+
bForm: z
|
|
14
|
+
.string()
|
|
15
|
+
.min(1, "B. Form is required")
|
|
16
|
+
.max(15, "B. Form must be 15 characters")
|
|
17
|
+
.regex(/^\d{5}-\d{7}-\d{1}$/, "B. Form must be in format XXXXX-XXXXXXX-X"),
|
|
18
|
+
dob: z
|
|
19
|
+
.string()
|
|
20
|
+
.min(1, "Date of birth is required")
|
|
21
|
+
.refine((dateStr) => {
|
|
22
|
+
const birthDate = new Date(dateStr);
|
|
23
|
+
const today = new Date();
|
|
24
|
+
const age = today.getFullYear() - birthDate.getFullYear();
|
|
25
|
+
// Adjust age if birthday hasn't occurred this year
|
|
26
|
+
const adjustedAge = today.getMonth() < birthDate.getMonth() ||
|
|
27
|
+
(today.getMonth() === birthDate.getMonth() &&
|
|
28
|
+
today.getDate() < birthDate.getDate())
|
|
29
|
+
? age - 1
|
|
30
|
+
: age;
|
|
31
|
+
return adjustedAge >= 2 && adjustedAge <= 25;
|
|
32
|
+
}, "Student age must be between 2 and 25 years"),
|
|
33
|
+
gender: z.nativeEnum(GENDER, { error: "Gender is required" }),
|
|
34
|
+
discountCode: z.string().optional(),
|
|
35
|
+
hafiz: z.boolean().optional(),
|
|
36
|
+
orphan: z.boolean().optional(),
|
|
37
|
+
// Father Information
|
|
38
|
+
fatherFirstName: z.string().min(1, "Father's first name is required"),
|
|
39
|
+
fatherLastName: z.string().min(1, "Father's last name is required"),
|
|
40
|
+
fatherCnic: z
|
|
41
|
+
.string()
|
|
42
|
+
.min(1, "Father's CNIC is required")
|
|
43
|
+
.max(15, "Father's CNIC must be 15 characters")
|
|
44
|
+
.regex(/^\d{5}-\d{7}-\d{1}$/, "Father's CNIC must be in format XXXXX-XXXXXXX-X"),
|
|
45
|
+
fatherMobile: z.string().min(1, "Father's mobile number is required"),
|
|
46
|
+
fatherOccupation: z.string().optional(),
|
|
47
|
+
fatherOrganization: z.string().optional(),
|
|
48
|
+
// Mother Information
|
|
49
|
+
motherFirstName: z.string().min(1, "Mother's first name is required"),
|
|
50
|
+
motherLastName: z.string().min(1, "Mother's last name is required"),
|
|
51
|
+
motherCnic: z
|
|
52
|
+
.string()
|
|
53
|
+
.min(1, "Mother's CNIC is required")
|
|
54
|
+
.max(15, "Mother's CNIC must be 15 characters")
|
|
55
|
+
.regex(/^\d{5}-\d{7}-\d{1}$/, "Mother's CNIC must be in format XXXXX-XXXXXXX-X"),
|
|
56
|
+
motherMobile: z.string().min(1, "Mother's mobile number is required"),
|
|
57
|
+
// Home Information
|
|
58
|
+
address: z.string().min(1, "Home address is required"),
|
|
59
|
+
city: z.string().min(1, "City is required"),
|
|
60
|
+
state: z.string().min(1, "State is required"),
|
|
61
|
+
country: z.string().min(1, "Country is required"),
|
|
62
|
+
// Admission Information
|
|
63
|
+
classForAdmission: z.string().min(1, "Class for admission is required"),
|
|
64
|
+
previousSchool: z.string().optional(),
|
|
65
|
+
siblings: z.string().optional(),
|
|
66
|
+
// Office Use
|
|
67
|
+
notes: z.string().optional(),
|
|
68
|
+
admissionNotes: z.string().optional(),
|
|
69
|
+
emergencyContact: z.string().optional(),
|
|
70
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const AdmissionView: () => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* Admission View Component
|
|
5
|
+
*
|
|
6
|
+
* Read-only details view for an admission record. Uses the module state pattern:
|
|
7
|
+
* `const { state, handleAnalyze } = useAdmissionModule()`
|
|
8
|
+
*
|
|
9
|
+
* Note: the `state` field in `moduleState` (province/state) is aliased
|
|
10
|
+
* to `provinceState` to avoid a name collision with the hook's `state` object.
|
|
11
|
+
*/
|
|
12
|
+
import { useAdmissionModule } from "./context";
|
|
13
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@appcorp/shadcn/components/ui/card";
|
|
14
|
+
import { Badge } from "@appcorp/shadcn/components/ui/badge";
|
|
15
|
+
import { Separator } from "@appcorp/shadcn/components/ui/separator";
|
|
16
|
+
import { AlertTriangle, BookOpen, Brain, Calendar, CheckCircle2, FileText, Home, Loader2, RefreshCw, ShieldCheck, ShieldX, User, XCircle, } from "lucide-react";
|
|
17
|
+
import { Button } from "@appcorp/shadcn/components/ui/button";
|
|
18
|
+
import { useTranslations } from "next-intl";
|
|
19
|
+
import { formatValue } from "../../utils/format-value";
|
|
20
|
+
import { formatDate, DATE_FORMATS } from "@react-pakistan/util-functions";
|
|
21
|
+
export const AdmissionView = () => {
|
|
22
|
+
const { state, handleAnalyze } = useAdmissionModule();
|
|
23
|
+
const { address, admissionNotes, aiAnalysis, analyzeLoading, analyzeError, bForm, city, classForAdmission, country, discountCode, dob, emergencyContact, enabled, fatherCnic, fatherFirstName, fatherLastName, fatherMobile, fatherOccupation, fatherOrganization, firstName, gender, hafiz, id, lastName, motherCnic, motherFirstName, motherLastName, motherMobile, notes, orphan, postalCode, previousSchool, registrationCode, siblings, state: provinceState, status, } = state;
|
|
24
|
+
const t = useTranslations("admission");
|
|
25
|
+
const decisionColor = (decision) => {
|
|
26
|
+
if (decision === "APPROVED")
|
|
27
|
+
return "bg-green-100 text-green-800 border-green-200";
|
|
28
|
+
if (decision === "REJECTED")
|
|
29
|
+
return "bg-red-100 text-red-800 border-red-200";
|
|
30
|
+
return "bg-yellow-100 text-yellow-800 border-yellow-200";
|
|
31
|
+
};
|
|
32
|
+
const decisionIcon = (decision) => {
|
|
33
|
+
if (decision === "APPROVED")
|
|
34
|
+
return _jsx(ShieldCheck, { className: "h-4 w-4" });
|
|
35
|
+
if (decision === "REJECTED")
|
|
36
|
+
return _jsx(ShieldX, { className: "h-4 w-4" });
|
|
37
|
+
return _jsx(AlertTriangle, { className: "h-4 w-4" });
|
|
38
|
+
};
|
|
39
|
+
return (_jsxs("div", { className: "space-y-6", children: [_jsxs(Card, { className: "border-primary/20 border-2", children: [_jsxs(CardHeader, { className: "pb-3", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Brain, { className: "text-primary h-5 w-5" }), _jsx(CardTitle, { className: "text-lg", children: t("aiAnalysis") })] }), _jsxs(Button, { variant: "outline", size: "sm", disabled: analyzeLoading, onClick: () => handleAnalyze({ id }), children: [analyzeLoading ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(RefreshCw, { className: "mr-2 h-4 w-4" })), aiAnalysis ? t("reRunAnalysis") : t("analyzeWithAi")] })] }), _jsx(CardDescription, { children: t("geminiPoweredAdmissionScoring") })] }), _jsx(Separator, {}), _jsxs(CardContent, { className: "pt-6", children: [analyzeLoading && (_jsxs("div", { className: "text-muted-foreground flex items-center gap-3", children: [_jsx(Loader2, { className: "h-5 w-5 animate-spin" }), _jsx("span", { children: t("runningAiAnalysis") })] })), analyzeError && !analyzeLoading && (_jsxs("div", { className: "text-destructive flex items-center gap-2 text-sm", children: [_jsx(AlertTriangle, { className: "h-4 w-4" }), _jsx("span", { children: analyzeError })] })), !analyzeLoading && aiAnalysis && (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-4", children: [_jsxs("div", { className: "bg-muted flex flex-col items-center rounded-lg px-6 py-3", children: [_jsx("span", { className: "text-primary text-3xl font-bold", children: aiAnalysis.score }), _jsx("span", { className: "text-muted-foreground text-xs tracking-wide uppercase", children: "/ 100" })] }), _jsxs("span", { className: `inline-flex items-center gap-1.5 rounded-full border px-3 py-1.5 text-sm font-medium ${decisionColor(aiAnalysis.decision)}`, children: [decisionIcon(aiAnalysis.decision), aiAnalysis.decision.replace("_", " ")] }), _jsxs("div", { className: "text-muted-foreground text-sm", children: [_jsxs("span", { className: "font-medium", children: [t("confidence"), ": "] }), (aiAnalysis.confidence * 100).toFixed(0), "%"] })] }), aiAnalysis.reasons.length > 0 && (_jsxs("div", { children: [_jsx("p", { className: "mb-2 text-sm font-medium", children: t("aiReasoningAndRiskFlags") }), _jsx("ul", { className: "space-y-1.5", children: aiAnalysis.reasons.map((reason, i) => (_jsxs("li", { className: "flex items-start gap-2 text-sm", children: [_jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0 text-yellow-500" }), _jsx("span", { children: reason })] }, i))) })] })), _jsxs("p", { className: "text-muted-foreground text-xs", children: [t("lastAnalyzed"), ":", " ", formatDate(String(aiAnalysis.processedAt), DATE_FORMATS.LOCALE_DATE)] })] })), !analyzeLoading && !aiAnalysis && !analyzeError && (_jsx("p", { className: "text-muted-foreground text-sm", children: t("noAiAnalysisYet") }))] })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "pb-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(BookOpen, { className: "text-primary h-5 w-5" }), _jsx(CardTitle, { className: "text-lg", children: t("admissionDetails") })] }), _jsx(CardDescription, { children: t("registrationAndAdmissionInformation") })] }), _jsx(Separator, {}), _jsx(CardContent, { className: "pt-6", children: _jsxs("div", { className: "grid grid-cols-1 gap-6 md:grid-cols-2", children: [_jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("registrationCode") }), _jsx("p", { className: "font-mono text-base", children: formatValue(registrationCode) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("classForAdmission") }), _jsx("p", { className: "text-base", children: formatValue(classForAdmission) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("status") }), _jsx("p", { className: "text-base", children: formatValue(status) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("previousSchool") }), _jsx("p", { className: "text-base", children: formatValue(previousSchool) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("siblings") }), _jsx("p", { className: "text-base", children: formatValue(siblings) })] })] }) })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "pb-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(User, { className: "text-primary h-5 w-5" }), _jsx(CardTitle, { className: "text-lg", children: t("studentInformation") })] }), _jsx(CardDescription, { children: t("personalDetails") })] }), _jsx(Separator, {}), _jsx(CardContent, { className: "pt-6", children: _jsxs("div", { className: "grid grid-cols-1 gap-6 md:grid-cols-2", children: [_jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("fullName") }), _jsxs("p", { className: "text-base", children: [formatValue(firstName), " ", formatValue(lastName)] })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("dob") }), _jsx("p", { className: "text-base", children: formatDate(String(dob), DATE_FORMATS.LOCALE_DATE) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("gender") }), _jsx("p", { className: "text-base", children: formatValue(gender) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("bForm") }), _jsx("p", { className: "text-base", children: formatValue(bForm) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("discountCode") }), _jsx("p", { className: "text-base", children: formatValue(discountCode) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("emergencyContact") }), _jsx("p", { className: "text-base", children: formatValue(emergencyContact) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("hafizEQuran") }), _jsx("p", { className: "text-base", children: hafiz ? t("yes") : t("no") })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("orphan") }), _jsx("p", { className: "text-base", children: orphan ? t("yes") : t("no") })] })] }) })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "pb-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(User, { className: "text-primary h-5 w-5" }), _jsx(CardTitle, { className: "text-lg", children: t("fatherInformation") })] }), _jsx(CardDescription, { children: t("fatherDetails") })] }), _jsx(Separator, {}), _jsx(CardContent, { className: "pt-6", children: _jsxs("div", { className: "grid grid-cols-1 gap-6 md:grid-cols-2", children: [_jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("fullName") }), _jsxs("p", { className: "text-base", children: [formatValue(fatherFirstName), " ", formatValue(fatherLastName)] })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("cnic") }), _jsx("p", { className: "text-base", children: formatValue(fatherCnic) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("mobile") }), _jsx("p", { className: "text-base", children: formatValue(fatherMobile) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("occupation") }), _jsx("p", { className: "text-base", children: formatValue(fatherOccupation) })] }), _jsxs("div", { className: "space-y-1 md:col-span-2", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("organization") }), _jsx("p", { className: "text-base", children: formatValue(fatherOrganization) })] })] }) })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "pb-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(User, { className: "text-primary h-5 w-5" }), _jsx(CardTitle, { className: "text-lg", children: t("motherInformation") })] }), _jsx(CardDescription, { children: t("motherDetails") })] }), _jsx(Separator, {}), _jsx(CardContent, { className: "pt-6", children: _jsxs("div", { className: "grid grid-cols-1 gap-6 md:grid-cols-2", children: [_jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("fullName") }), _jsxs("p", { className: "text-base", children: [formatValue(motherFirstName), " ", formatValue(motherLastName)] })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("cnic") }), _jsx("p", { className: "text-base", children: formatValue(motherCnic) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("mobile") }), _jsx("p", { className: "text-base", children: formatValue(motherMobile) })] })] }) })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "pb-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Home, { className: "text-primary h-5 w-5" }), _jsx(CardTitle, { className: "text-lg", children: t("homeDetails") })] }), _jsx(CardDescription, { children: t("addressInformation") })] }), _jsx(Separator, {}), _jsx(CardContent, { className: "pt-6", children: _jsxs("div", { className: "grid grid-cols-1 gap-6 md:grid-cols-2", children: [_jsxs("div", { className: "space-y-1 md:col-span-2", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("address") }), _jsx("p", { className: "text-base", children: formatValue(address) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("city") }), _jsx("p", { className: "text-base", children: formatValue(city) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("state") }), _jsx("p", { className: "text-base", children: formatValue(provinceState) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("country") }), _jsx("p", { className: "text-base", children: formatValue(country) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("postalCode") }), _jsx("p", { className: "text-base", children: formatValue(postalCode) })] })] }) })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "pb-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(FileText, { className: "text-primary h-5 w-5" }), _jsx(CardTitle, { className: "text-lg", children: t("officeUse") })] }), _jsx(CardDescription, { children: t("internalNotesAndProcessingInformation") })] }), _jsx(Separator, {}), _jsx(CardContent, { className: "pt-6", children: _jsxs("div", { className: "grid grid-cols-1 gap-6", children: [_jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("notes") }), _jsx("p", { className: "text-base", children: formatValue(notes) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("admissionNotes") }), _jsx("p", { className: "text-base", children: formatValue(admissionNotes) })] })] }) })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "pb-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Calendar, { className: "text-primary h-5 w-5" }), _jsx(CardTitle, { className: "text-lg", children: t("recordStatus") })] }), _jsx(CardDescription, { children: t("recordStateAndMetadata") })] }), _jsx(Separator, {}), _jsx(CardContent, { className: "pt-6", children: _jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-muted-foreground text-sm font-medium", children: t("enabled") }), _jsxs(Badge, { variant: enabled ? "default" : "destructive", className: "gap-1", children: [enabled ? (_jsx(CheckCircle2, { className: "h-3 w-3" })) : (_jsx(XCircle, { className: "h-3 w-3" })), enabled ? t("active") : t("inactive")] })] }) })] })] }));
|
|
40
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discount Code Cache utilities
|
|
3
|
+
*
|
|
4
|
+
* Convenience wrappers around the shared cache helpers used across modules.
|
|
5
|
+
* These helpers are intentionally small: callers in the UI layer expect
|
|
6
|
+
* simple, well-typed functions such as `getCachedDiscountCodes` and
|
|
7
|
+
* `invalidateDiscountCodeCache`.
|
|
8
|
+
*
|
|
9
|
+
* Usage examples:
|
|
10
|
+
* - `await getCachedDiscountCodes({ params: { page: 1 } })`
|
|
11
|
+
* - `invalidateDiscountCodeCache()` after a successful create/update/delete
|
|
12
|
+
*/
|
|
13
|
+
import { DiscountCodeBE } from "@/type";
|
|
14
|
+
/** Synchronous access to cached discount codes (localStorage) */
|
|
15
|
+
export declare const getCachedDiscountCodesSync: () => import("@react-pakistan/util-functions").ListResponse<DiscountCodeBE>;
|
|
16
|
+
/** Fetch cached list (may call network if cache miss or stale) */
|
|
17
|
+
export declare const getCachedDiscountCodes: ({ params, }: {
|
|
18
|
+
params: Record<string, unknown>;
|
|
19
|
+
}) => Promise<import("@react-pakistan/util-functions").ListResponse<DiscountCodeBE>>;
|
|
20
|
+
/** Get a single discount code from the cache by id */
|
|
21
|
+
export declare const getCachedDiscountCodeById: (discountCodeId: string) => DiscountCodeBE | null;
|
|
22
|
+
/** Remove discount-code cache entries (used after writes) */
|
|
23
|
+
export declare const invalidateDiscountCodeCache: () => void;
|
|
24
|
+
/** Preload discount codes into cache (useful during bootstrap) */
|
|
25
|
+
export declare const preloadDiscountCodes: () => Promise<import("@react-pakistan/util-functions").ListResponse<DiscountCodeBE>>;
|
|
26
|
+
/** Utility to check if the discount codes cache is stale */
|
|
27
|
+
export declare const isDiscountCodesCacheStale: () => boolean;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discount Code Cache utilities
|
|
3
|
+
*
|
|
4
|
+
* Convenience wrappers around the shared cache helpers used across modules.
|
|
5
|
+
* These helpers are intentionally small: callers in the UI layer expect
|
|
6
|
+
* simple, well-typed functions such as `getCachedDiscountCodes` and
|
|
7
|
+
* `invalidateDiscountCodeCache`.
|
|
8
|
+
*
|
|
9
|
+
* Usage examples:
|
|
10
|
+
* - `await getCachedDiscountCodes({ params: { page: 1 } })`
|
|
11
|
+
* - `invalidateDiscountCodeCache()` after a successful create/update/delete
|
|
12
|
+
*/
|
|
13
|
+
import { LS_KEYS } from "@/constants";
|
|
14
|
+
import { DISCOUNT_CODE_API_ROUTES } from "./constants";
|
|
15
|
+
import { getCachedData, getCachedDataSync, getCachedItemById, invalidateCache, isCacheStale, preloadCache, } from "@react-pakistan/util-functions";
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// CACHE CONFIGURATION
|
|
18
|
+
// ============================================================================
|
|
19
|
+
const DISCOUNT_CODE_CACHE_CONFIG = {
|
|
20
|
+
cacheKey: LS_KEYS.DISCOUNT_CODES,
|
|
21
|
+
apiUrl: DISCOUNT_CODE_API_ROUTES.UNIT,
|
|
22
|
+
};
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// DISCOUNT CODE CACHE FUNCTIONS
|
|
25
|
+
// ============================================================================
|
|
26
|
+
/** Synchronous access to cached discount codes (localStorage) */
|
|
27
|
+
export const getCachedDiscountCodesSync = () => getCachedDataSync(LS_KEYS.DISCOUNT_CODES);
|
|
28
|
+
/** Fetch cached list (may call network if cache miss or stale) */
|
|
29
|
+
export const getCachedDiscountCodes = ({ params, }) => getCachedData({
|
|
30
|
+
config: DISCOUNT_CODE_CACHE_CONFIG,
|
|
31
|
+
params,
|
|
32
|
+
headers: {
|
|
33
|
+
"Content-Type": "application/json",
|
|
34
|
+
"x-api-token": process.env.NEXT_PUBLIC_API_KEY,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
/** Get a single discount code from the cache by id */
|
|
38
|
+
export const getCachedDiscountCodeById = (discountCodeId) => getCachedItemById(LS_KEYS.DISCOUNT_CODES, discountCodeId);
|
|
39
|
+
/** Remove discount-code cache entries (used after writes) */
|
|
40
|
+
export const invalidateDiscountCodeCache = () => {
|
|
41
|
+
invalidateCache(LS_KEYS.DISCOUNT_CODES);
|
|
42
|
+
};
|
|
43
|
+
/** Preload discount codes into cache (useful during bootstrap) */
|
|
44
|
+
export const preloadDiscountCodes = () => preloadCache(DISCOUNT_CODE_CACHE_CONFIG);
|
|
45
|
+
/** Utility to check if the discount codes cache is stale */
|
|
46
|
+
export const isDiscountCodesCacheStale = () => isCacheStale(LS_KEYS.DISCOUNT_CODES);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discount Code Constants
|
|
3
|
+
*
|
|
4
|
+
* Centralized constants used by the discount-code module. Keep values
|
|
5
|
+
* environment-driven (via `NEXT_PUBLIC_*`) where appropriate so CI/infra
|
|
6
|
+
* can override them without code changes.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Default page limit for the discount codes list. Controlled by
|
|
10
|
+
* `NEXT_PUBLIC_PAGE_LIMIT` with a safe fallback to `10`.
|
|
11
|
+
*/
|
|
12
|
+
export declare const pageLimit: number;
|
|
13
|
+
/**
|
|
14
|
+
* API routes for discount-code operations. The backend expects requests to
|
|
15
|
+
* these endpoints and returns the standard `{ items, count }` list shape.
|
|
16
|
+
*/
|
|
17
|
+
export declare const DISCOUNT_CODE_API_ROUTES: {
|
|
18
|
+
UNIT: string;
|
|
19
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discount Code Constants
|
|
3
|
+
*
|
|
4
|
+
* Centralized constants used by the discount-code module. Keep values
|
|
5
|
+
* environment-driven (via `NEXT_PUBLIC_*`) where appropriate so CI/infra
|
|
6
|
+
* can override them without code changes.
|
|
7
|
+
*/
|
|
8
|
+
"use client";
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// PAGE CONFIGURATION
|
|
11
|
+
// ============================================================================
|
|
12
|
+
/**
|
|
13
|
+
* Default page limit for the discount codes list. Controlled by
|
|
14
|
+
* `NEXT_PUBLIC_PAGE_LIMIT` with a safe fallback to `10`.
|
|
15
|
+
*/
|
|
16
|
+
export const pageLimit = Number(process.env.NEXT_PUBLIC_PAGE_LIMIT) || 10;
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// API ROUTES
|
|
19
|
+
// ============================================================================
|
|
20
|
+
/**
|
|
21
|
+
* API routes for discount-code operations. The backend expects requests to
|
|
22
|
+
* these endpoints and returns the standard `{ items, count }` list shape.
|
|
23
|
+
*/
|
|
24
|
+
export const DISCOUNT_CODE_API_ROUTES = {
|
|
25
|
+
UNIT: "/api/v1/discount-code",
|
|
26
|
+
};
|