Merge pull request 'azeem' (#49) from azeem into development
Reviewed-on: https://gitea.maskantech.in/gitea_admin/aeroresolve_frontend/pulls/49
This commit is contained in:
@@ -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<OptionItem[]>([]);
|
||||
@@ -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() {
|
||||
<div className="flex flex-col gap-2">
|
||||
<CustomMultiSelect
|
||||
label='Jurisdiction'
|
||||
required
|
||||
options={jurisdictionOptions}
|
||||
value={jurisdiction}
|
||||
onChange={setJurisdiction}
|
||||
@@ -890,7 +937,7 @@ export default function AddPolicyEngine() {
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-[13px] font-semibold text-gray-700">Status</label>
|
||||
<label className="text-[13px] font-semibold text-gray-700">Status<span className="text-red-500 ml-1">*</span></label>
|
||||
<div className="flex items-center gap-6 h-11">
|
||||
<CustomRadio
|
||||
name="status"
|
||||
@@ -911,6 +958,7 @@ export default function AddPolicyEngine() {
|
||||
<div className="flex flex-col gap-2">
|
||||
<CustomTextArea
|
||||
label='Description'
|
||||
required
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
placeholder="Enter description..."
|
||||
@@ -943,6 +991,7 @@ export default function AddPolicyEngine() {
|
||||
<div className="w-1/3 flex flex-col gap-2">
|
||||
<CustomMultiSelect
|
||||
label='Cohort'
|
||||
required
|
||||
options={cohortOptions}
|
||||
value={selectedCohorts}
|
||||
onChange={setSelectedCohorts}
|
||||
@@ -989,6 +1038,7 @@ export default function AddPolicyEngine() {
|
||||
<div className="w-1/3 flex flex-col gap-2">
|
||||
<CustomDropdown
|
||||
label='Rule Category'
|
||||
required
|
||||
options={ruleCategoryOptions}
|
||||
value={rule.category}
|
||||
onChange={(val) => handleCategoryChange(rule.id, val)}
|
||||
@@ -996,7 +1046,7 @@ export default function AddPolicyEngine() {
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-[14px] font-medium leading-none text-[#001811]">Priority</label>
|
||||
<label className="text-[14px] font-medium leading-none text-[#001811]">Priority<span className="text-red-500 ml-1">*</span></label>
|
||||
{(() => {
|
||||
const prevPriority = getPrevPriority(rule);
|
||||
const nextPriority = getNextPriority(rule);
|
||||
@@ -1044,6 +1094,7 @@ export default function AddPolicyEngine() {
|
||||
<div className="flex-1 flex flex-col gap-2">
|
||||
<CustomDropdown
|
||||
label='Condition'
|
||||
required
|
||||
options={categoryConditionMap[rule.category] || []}
|
||||
value={condition.condition}
|
||||
onChange={(val) => handleConditionFieldSelect(rule.id, condition.id, val)}
|
||||
@@ -1054,6 +1105,7 @@ export default function AddPolicyEngine() {
|
||||
<div className="flex-1 flex flex-col gap-2">
|
||||
<CustomDropdown
|
||||
label='Operator'
|
||||
required
|
||||
options={operatorOptions}
|
||||
value={condition.operator}
|
||||
onChange={(val) => handleUpdateCondition(rule.id, condition.id, { operator: val })}
|
||||
@@ -1064,6 +1116,7 @@ export default function AddPolicyEngine() {
|
||||
{isLookup ? (
|
||||
<CustomDropdown
|
||||
label='Value'
|
||||
required
|
||||
options={lookupOpts}
|
||||
value={condition.value}
|
||||
onChange={(val) => handleUpdateCondition(rule.id, condition.id, { value: val })}
|
||||
@@ -1072,6 +1125,7 @@ export default function AddPolicyEngine() {
|
||||
) : isBoolean ? (
|
||||
<CustomDropdown
|
||||
label='Value'
|
||||
required
|
||||
options={[
|
||||
{ label: 'True', value: 'true' },
|
||||
{ label: 'False', value: 'false' },
|
||||
@@ -1083,6 +1137,7 @@ export default function AddPolicyEngine() {
|
||||
) : (
|
||||
<CustomInput
|
||||
label='Value'
|
||||
required
|
||||
type={isNumber ? 'number' : 'text'}
|
||||
value={condition.value}
|
||||
onChange={(e) => handleUpdateCondition(rule.id, condition.id, { value: e.target.value })}
|
||||
@@ -1092,7 +1147,7 @@ export default function AddPolicyEngine() {
|
||||
</div>
|
||||
{cIdx < rule.conditions.length - 1 ? (
|
||||
<div className="w-[163px] flex flex-col gap-2">
|
||||
<label className="text-[14px] font-medium leading-none text-[#001811]">Logic</label>
|
||||
<label className="text-[14px] font-medium leading-none text-[#001811]">Logic<span className="text-red-500 ml-1">*</span></label>
|
||||
<LogicDropdown
|
||||
value={condition.logic}
|
||||
onChange={(val) => handleUpdateCondition(rule.id, condition.id, { logic: val })}
|
||||
@@ -1140,6 +1195,7 @@ export default function AddPolicyEngine() {
|
||||
<div className="flex-1 flex flex-col gap-2">
|
||||
<CustomDropdown
|
||||
label="Action Category"
|
||||
required
|
||||
options={actionCategoryOptions}
|
||||
value={action.actionCategoryId}
|
||||
onChange={(val) => handleActionCategoryChange(rule.id, action.id, val)}
|
||||
@@ -1150,6 +1206,7 @@ export default function AddPolicyEngine() {
|
||||
<div className="flex-1 flex flex-col gap-2">
|
||||
<CustomDropdown
|
||||
label="Action Type"
|
||||
required
|
||||
options={actionTypesByCategoryMap[action.actionCategoryId] || []}
|
||||
value={action.actionTypeId}
|
||||
onChange={(val) => handleActionTypeChange(rule.id, action.id, val)}
|
||||
@@ -1160,7 +1217,7 @@ export default function AddPolicyEngine() {
|
||||
|
||||
{aIdx < rule.actions.length - 1 ? (
|
||||
<div className="w-[163px] flex flex-col gap-2">
|
||||
<label className="text-[14px] font-medium leading-none text-[#001811]">Logic</label>
|
||||
<label className="text-[14px] font-medium leading-none text-[#001811]">Logic<span className="text-red-500 ml-1">*</span></label>
|
||||
<LogicDropdown value={action.logic} readOnly />
|
||||
</div>
|
||||
) : (
|
||||
@@ -1329,16 +1386,14 @@ export default function AddPolicyEngine() {
|
||||
<CustomStatus status={status} />
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
{!isEditMode && (
|
||||
<CustomButton
|
||||
variant="secondary"
|
||||
className="!text-[#1E7D5C] !bg-[#E8F3EF] hover:!bg-[#d9ece4] !border-none font-semibold px-6"
|
||||
onClick={() => handleSavePolicy(false)}
|
||||
disabled={isSaving}
|
||||
>
|
||||
{isSaving ? 'Saving...' : 'Save Draft'}
|
||||
</CustomButton>
|
||||
)}
|
||||
<CustomButton
|
||||
variant="secondary"
|
||||
className="!text-[#1E7D5C] !bg-[#E8F3EF] hover:!bg-[#d9ece4] !border-none font-semibold px-6 disabled:opacity-50"
|
||||
onClick={() => handleSavePolicy(false)}
|
||||
disabled={isSaving || !isDraftValid}
|
||||
>
|
||||
{isSaving ? 'Saving...' : 'Save Draft'}
|
||||
</CustomButton>
|
||||
<CustomButton
|
||||
variant="outlined"
|
||||
className="!border-[#1E7D5C] !text-[#1E7D5C] hover:!bg-gray-50 font-semibold px-6"
|
||||
@@ -1349,29 +1404,15 @@ export default function AddPolicyEngine() {
|
||||
</CustomButton>
|
||||
<CustomButton
|
||||
variant="primary"
|
||||
className="!bg-[#1E7D5C] hover:!bg-[#17664B] font-semibold px-6 shadow-sm"
|
||||
className="!bg-[#1E7D5C] hover:!bg-[#17664B] font-semibold px-6 shadow-sm disabled:opacity-50"
|
||||
onClick={() => handleSavePolicy(true)}
|
||||
disabled={isSaving}
|
||||
disabled={isSaving || !isFormValid}
|
||||
>
|
||||
{isSaving ? 'Deploying...' : isEditMode ? 'Update & Deploy Policy' : 'Deploy Policy'}
|
||||
</CustomButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ─── Success Modal ──────────────────────────────────────────────── */}
|
||||
<CustomSuccessModal
|
||||
isOpen={showSuccessModal}
|
||||
onClose={() => {
|
||||
setShowSuccessModal(false);
|
||||
navigate('/policy-engine');
|
||||
}}
|
||||
title={isEditMode ? 'Policy Updated Successfully.' : 'Policy Created Successfully.'}
|
||||
label="POLICY NAME"
|
||||
cohortName={policyName}
|
||||
cohortStatus={status}
|
||||
cohortDescription={description}
|
||||
/>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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<string | null>(null);
|
||||
const [successMsg, setSuccessMsg] = useState<string | null>(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);
|
||||
|
||||
@@ -95,19 +95,27 @@ export default function RecoveryIncidentsList() {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [successMsg, setSuccessMsg] = useState<string | null>(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<Set<string>>(new Set());
|
||||
const [isGrouped, setIsGrouped] = useState(() => {
|
||||
return sessionStorage.getItem("recovery_isGrouped") === "true";
|
||||
});
|
||||
const [collapsedGroups, setCollapsedGroups] = useState<Set<string>>(() => {
|
||||
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<RecoveryIncident | null>(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);
|
||||
|
||||
Reference in New Issue
Block a user