diff --git a/src/app/policyEngine/components/AddPolicyEngine.tsx b/src/app/policyEngine/components/AddPolicyEngine.tsx index 5c31b1f..ae47969 100644 --- a/src/app/policyEngine/components/AddPolicyEngine.tsx +++ b/src/app/policyEngine/components/AddPolicyEngine.tsx @@ -17,7 +17,6 @@ import { CustomLoader, Skeleton, } from '../../../components/custom'; -import CustomSuccessModal from '../../../components/custom/CustomSuccessModal'; import { getJurisdictionOptions, getCohortOptions, @@ -153,7 +152,6 @@ export default function AddPolicyEngine() { const [loadingPolicy, setLoadingPolicy] = useState(false); const [isSaving, setIsSaving] = useState(false); - const [showSuccessModal, setShowSuccessModal] = useState(false); // Dynamic Options State (loaded directly from PolicyEngineApi) const [jurisdictionOptions, setJurisdictionOptions] = useState([]); @@ -338,7 +336,7 @@ export default function AddPolicyEngine() { // Policy Info State const [policyName, setPolicyName] = useState(''); const [jurisdiction, setJurisdiction] = useState<(string | number)[]>([]); - const [status, setStatus] = useState<'Active' | 'Inactive'>('Active'); + const [status, setStatus] = useState<'Active' | 'Inactive' | 'Draft'>('Active'); const [description, setDescription] = useState(''); // Target Audience State @@ -381,7 +379,8 @@ export default function AddPolicyEngine() { setJurisdiction(singleJur ? [singleJur] : []); } setStatus( - data.status?.toLowerCase() === 'active' ? 'Active' : 'Inactive' + data.status?.toLowerCase() === 'active' ? 'Active' : + data.status?.toLowerCase() === 'draft' ? 'Draft' : 'Inactive' ); setDescription(data.description || ''); @@ -532,9 +531,49 @@ export default function AddPolicyEngine() { .finally(() => setLoadingPolicy(false)); }, [policyId]); + const isFormValid = (() => { + if (!policyName.trim()) return false; + + if (audienceType === 'Selected Cohorts' && (!selectedCohorts || selectedCohorts.length === 0)) return false; + + if (!rules || rules.length === 0) return false; + + for (const rule of rules) { + if (!rule.category) return false; + + if (!rule.conditions || rule.conditions.length === 0) return false; + for (const cond of rule.conditions) { + if (!cond.condition || !cond.operator || cond.value === '' || cond.value === undefined || cond.value === null) return false; + } + + if (!rule.actions || rule.actions.length === 0) return false; + for (const act of rule.actions) { + if (!act.actionCategoryId || !act.actionTypeId) return false; + + const fields = actionTypeFieldsMap[act.actionTypeId] || []; + for (const field of fields) { + if (field.isRequired) { + const val = act.fieldValues?.[field.fieldCode]; + if (val === undefined || val === null || val === '') return false; + if (Array.isArray(val) && val.length === 0) return false; + if (typeof val === 'object' && !Array.isArray(val)) { + if (val.amount === undefined || val.amount === null || val.amount === '') return false; + } + } + } + } + } + + return true; + })(); + + const isDraftValid = policyName.trim().length > 0; + const handleSavePolicy = async (isDeploy: boolean) => { - if (!policyName.trim()) { - alert('Please enter a Policy Name.'); + if (isDeploy && !isFormValid) { + return; + } + if (!isDeploy && !isDraftValid) { return; } @@ -545,7 +584,7 @@ export default function AddPolicyEngine() { jurisdictionId: Array.isArray(jurisdiction) && jurisdiction.length > 0 ? String(jurisdiction[0]) : undefined, jurisdictionIds: Array.isArray(jurisdiction) ? jurisdiction.map(String) : [], description: description || undefined, - status: isDeploy ? status.toLowerCase() : 'draft', + status: isDeploy ? (status === 'Draft' ? 'active' : status.toLowerCase()) : 'draft', audienceType: audienceType === 'Selected Cohorts' ? 'COHORT' : 'ALL', targetAudiences: audienceType === 'Selected Cohorts' @@ -621,7 +660,14 @@ export default function AddPolicyEngine() { await createPolicy(payload); } - setShowSuccessModal(true); + let actionWord = isDeploy ? 'created' : 'saved as draft'; + if (isEditMode) { + actionWord = isDeploy ? 'updated' : 'saved as draft'; + } + + const successMsg = `Policy "${payload.policyName}" ${actionWord} successfully!`; + + navigate('/policy-engine', { state: { successMsg } }); } catch (err) { console.error('Failed to save policy', err); alert('Failed to save policy. Please check input values and try again.'); @@ -883,6 +929,7 @@ export default function AddPolicyEngine() {
- +
setDescription(e.target.value)} placeholder="Enter description..." @@ -943,6 +991,7 @@ export default function AddPolicyEngine() {
handleCategoryChange(rule.id, val)} @@ -996,7 +1046,7 @@ export default function AddPolicyEngine() { />
- + {(() => { const prevPriority = getPrevPriority(rule); const nextPriority = getNextPriority(rule); @@ -1044,6 +1094,7 @@ export default function AddPolicyEngine() {
handleConditionFieldSelect(rule.id, condition.id, val)} @@ -1054,6 +1105,7 @@ export default function AddPolicyEngine() {
handleUpdateCondition(rule.id, condition.id, { operator: val })} @@ -1064,6 +1116,7 @@ export default function AddPolicyEngine() { {isLookup ? ( handleUpdateCondition(rule.id, condition.id, { value: val })} @@ -1072,6 +1125,7 @@ export default function AddPolicyEngine() { ) : isBoolean ? ( handleUpdateCondition(rule.id, condition.id, { value: e.target.value })} @@ -1092,7 +1147,7 @@ export default function AddPolicyEngine() {
{cIdx < rule.conditions.length - 1 ? (
- + handleUpdateCondition(rule.id, condition.id, { logic: val })} @@ -1140,6 +1195,7 @@ export default function AddPolicyEngine() {
handleActionCategoryChange(rule.id, action.id, val)} @@ -1150,6 +1206,7 @@ export default function AddPolicyEngine() {
handleActionTypeChange(rule.id, action.id, val)} @@ -1160,7 +1217,7 @@ export default function AddPolicyEngine() { {aIdx < rule.actions.length - 1 ? (
- +
) : ( @@ -1329,16 +1386,14 @@ export default function AddPolicyEngine() {
- {!isEditMode && ( - handleSavePolicy(false)} - disabled={isSaving} - > - {isSaving ? 'Saving...' : 'Save Draft'} - - )} + handleSavePolicy(false)} + disabled={isSaving || !isDraftValid} + > + {isSaving ? 'Saving...' : 'Save Draft'} + handleSavePolicy(true)} - disabled={isSaving} + disabled={isSaving || !isFormValid} > {isSaving ? 'Deploying...' : isEditMode ? 'Update & Deploy Policy' : 'Deploy Policy'}
- {/* ─── Success Modal ──────────────────────────────────────────────── */} - { - setShowSuccessModal(false); - navigate('/policy-engine'); - }} - title={isEditMode ? 'Policy Updated Successfully.' : 'Policy Created Successfully.'} - label="POLICY NAME" - cohortName={policyName} - cohortStatus={status} - cohortDescription={description} - /> -
); } diff --git a/src/app/policyEngine/components/PolicyEngineList.tsx b/src/app/policyEngine/components/PolicyEngineList.tsx index 6415489..6878e56 100644 --- a/src/app/policyEngine/components/PolicyEngineList.tsx +++ b/src/app/policyEngine/components/PolicyEngineList.tsx @@ -7,7 +7,7 @@ import { PencilSimpleIcon, ChecksIcon, } from "@phosphor-icons/react"; -import { useNavigate } from "react-router-dom"; +import { useNavigate, useLocation } from "react-router-dom"; import { CustomTable, CustomInput, @@ -65,6 +65,14 @@ export default function PolicyEngineList() { const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [successMsg, setSuccessMsg] = useState(null); + const location = useLocation(); + + useEffect(() => { + if (location.state?.successMsg) { + setSuccessMsg(location.state.successMsg); + window.history.replaceState({}, document.title); + } + }, [location]); // Pagination const [currentPage, setCurrentPage] = useState(1); diff --git a/src/app/recoveryIncidents/components/RecoveryIncidentsList.tsx b/src/app/recoveryIncidents/components/RecoveryIncidentsList.tsx index a6d6f17..43ca927 100644 --- a/src/app/recoveryIncidents/components/RecoveryIncidentsList.tsx +++ b/src/app/recoveryIncidents/components/RecoveryIncidentsList.tsx @@ -95,19 +95,27 @@ export default function RecoveryIncidentsList() { const [error, setError] = useState(null); const [successMsg, setSuccessMsg] = useState(null); - useEffect(() => { - getRecoveryMetrics() - .then((data) => setMetrics(data)) - .catch((err) => console.error("Failed to fetch recovery metrics:", err)); - }, []); - // Pagination const [currentPage, setCurrentPage] = useState(1); // Filters const [search, setSearch] = useState(""); - const [isGrouped, setIsGrouped] = useState(false); - const [collapsedGroups, setCollapsedGroups] = useState>(new Set()); + const [isGrouped, setIsGrouped] = useState(() => { + return sessionStorage.getItem("recovery_isGrouped") === "true"; + }); + const [collapsedGroups, setCollapsedGroups] = useState>(() => { + const saved = sessionStorage.getItem("recovery_collapsedGroups"); + return saved ? new Set(JSON.parse(saved)) : new Set(); + }); + + useEffect(() => { + sessionStorage.setItem("recovery_isGrouped", String(isGrouped)); + }, [isGrouped]); + + useEffect(() => { + sessionStorage.setItem("recovery_collapsedGroups", JSON.stringify(Array.from(collapsedGroups))); + }, [collapsedGroups]); + const [isModalOpen, setIsModalOpen] = useState(false); const [editingIncident, setEditingIncident] = useState(null); @@ -129,6 +137,9 @@ export default function RecoveryIncidentsList() { try { const data = await getRecoveryIncidents(); setIncidents(data); + getRecoveryMetrics() + .then((m) => setMetrics(m)) + .catch((err) => console.error("Failed to fetch recovery metrics:", err)); } catch (error) { console.error("Failed to fetch recovery incidents", error); setIncidents([]); @@ -143,8 +154,13 @@ export default function RecoveryIncidentsList() { useEffect(() => { if (incidents.length > 0) { - const keys = new Set(incidents.map((i) => i.flightNumber || "Other")); - setCollapsedGroups(keys); + setCollapsedGroups((prev) => { + const hasSaved = sessionStorage.getItem("recovery_hasSavedGroups"); + if (hasSaved) return prev; + + sessionStorage.setItem("recovery_hasSavedGroups", "true"); + return new Set(incidents.map((i) => i.flightNumber || "Other")); + }); } }, [incidents]); @@ -194,10 +210,9 @@ export default function RecoveryIncidentsList() { try { setError(null); setSuccessMsg(null); - const { updateIncidentStatus, getRecoveryMetrics } = await import('../RecoveryIncidentsApi'); + const { updateIncidentStatus } = await import('../RecoveryIncidentsApi'); await updateIncidentStatus(incident.id, text); fetchIncidents(); - getRecoveryMetrics().then((data) => setMetrics(data)).catch(() => { }); setSuccessMsg(`Incident status updated to ${text} successfully.`); } catch (error) { console.error("Failed to update status", error);