feat: support editing plan details and deleting drafts with backend endpoints
This commit is contained in:
parent
bff2c3974e
commit
08fe3954a2
5 changed files with 141 additions and 18 deletions
|
|
@ -149,3 +149,54 @@ export const PUT = withAuth(async (req, { session, prisma, params }) => {
|
|||
return NextResponse.json({ plan: updatedPlan, versioned: false });
|
||||
}
|
||||
}, ['admin', 'director']);
|
||||
|
||||
// DELETE: Delete a plan (restricted to admin and director, and only allowed for DRAFT plans)
|
||||
export const DELETE = withAuth(async (req, { session, prisma, params }) => {
|
||||
const unwrappedParams = await params;
|
||||
const id = parseInt(unwrappedParams.id);
|
||||
|
||||
if (isNaN(id)) {
|
||||
return NextResponse.json({ error: 'ID no válido.' }, { status: 400 });
|
||||
}
|
||||
|
||||
const plan = await prisma.compensationPlan.findUnique({
|
||||
where: { id },
|
||||
include: { settlements: true }
|
||||
});
|
||||
|
||||
if (!plan) {
|
||||
return NextResponse.json({ error: 'Plan no encontrado.' }, { status: 404 });
|
||||
}
|
||||
|
||||
if (plan.status !== 'DRAFT') {
|
||||
return NextResponse.json(
|
||||
{ error: 'Solo se pueden eliminar planes en estado BORRADOR (DRAFT).' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
if (plan.settlements && plan.settlements.length > 0) {
|
||||
return NextResponse.json(
|
||||
{ error: 'No se puede eliminar el plan porque tiene liquidaciones asociadas.' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
await prisma.$transaction(async (tx: any) => {
|
||||
await createAuditLog(tx, {
|
||||
userId: session.userId,
|
||||
action: 'DELETE',
|
||||
targetTable: 'compensation_plans',
|
||||
targetId: id,
|
||||
previousValue: plan,
|
||||
newValue: null,
|
||||
ipAddress: req.headers.get('x-forwarded-for') || '127.0.0.1'
|
||||
});
|
||||
|
||||
await tx.compensationPlan.delete({
|
||||
where: { id }
|
||||
});
|
||||
});
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
}, ['admin', 'director']);
|
||||
|
|
|
|||
|
|
@ -230,6 +230,7 @@
|
|||
|
||||
.cardFooter {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-2);
|
||||
margin-top: auto;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,22 @@ export default function PlansPage() {
|
|||
const [maxCap, setMaxCap] = useState('');
|
||||
const [status, setStatus] = useState('DRAFT');
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [editPlanId, setEditPlanId] = useState<number | null>(null);
|
||||
|
||||
const handleCloseModal = () => {
|
||||
setName('');
|
||||
setCode('');
|
||||
setValidityStart('');
|
||||
setValidityEnd('');
|
||||
setType('PERCENTAGE');
|
||||
setMetaAmount('');
|
||||
setPercentageRate('');
|
||||
setMaxCap('');
|
||||
setStatus('DRAFT');
|
||||
setEditPlanId(null);
|
||||
setError(null);
|
||||
setShowModal(false);
|
||||
};
|
||||
|
||||
const fetchPlans = async () => {
|
||||
try {
|
||||
|
|
@ -80,8 +96,10 @@ export default function PlansPage() {
|
|||
}
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/plans', {
|
||||
method: 'POST',
|
||||
const url = editPlanId ? `/api/plans/${editPlanId}` : '/api/plans';
|
||||
const method = editPlanId ? 'PUT' : 'POST';
|
||||
const res = await fetch(url, {
|
||||
method,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
name,
|
||||
|
|
@ -99,27 +117,53 @@ export default function PlansPage() {
|
|||
const data = await res.json();
|
||||
|
||||
if (!res.ok) {
|
||||
setError(data.error || 'Failed to create plan');
|
||||
setError(data.error || 'Failed to submit plan');
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset form & reload
|
||||
setName('');
|
||||
setCode('');
|
||||
setValidityStart('');
|
||||
setValidityEnd('');
|
||||
setType('PERCENTAGE');
|
||||
setMetaAmount('');
|
||||
setPercentageRate('');
|
||||
setMaxCap('');
|
||||
setStatus('DRAFT');
|
||||
setShowModal(false);
|
||||
handleCloseModal();
|
||||
fetchPlans();
|
||||
} catch (err) {
|
||||
setError('An error occurred during submission.');
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditClick = (plan: Plan) => {
|
||||
setName(plan.name);
|
||||
setCode(plan.code);
|
||||
setValidityStart(plan.validityStart ? plan.validityStart.substring(0, 10) : '');
|
||||
setValidityEnd(plan.validityEnd ? plan.validityEnd.substring(0, 10) : '');
|
||||
setType(plan.type);
|
||||
setMetaAmount(plan.metaAmount ? plan.metaAmount.toString() : '');
|
||||
setPercentageRate(plan.percentageRate ? plan.percentageRate.toString() : '');
|
||||
setMaxCap(plan.maxCap ? plan.maxCap.toString() : '');
|
||||
setStatus(plan.status);
|
||||
setEditPlanId(plan.id);
|
||||
setShowModal(true);
|
||||
};
|
||||
|
||||
const handleDeletePlan = async (planId: number) => {
|
||||
if (!confirm(t('plans.confirmDelete'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/plans/${planId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const data = await res.json();
|
||||
alert(data.error || 'Failed to delete plan');
|
||||
return;
|
||||
}
|
||||
|
||||
fetchPlans();
|
||||
} catch (err) {
|
||||
alert('An error occurred while deleting the plan.');
|
||||
}
|
||||
};
|
||||
|
||||
const handleToggleStatus = async (plan: Plan) => {
|
||||
const nextStatus = plan.status === 'ACTIVE' ? 'INACTIVE' : 'ACTIVE';
|
||||
try {
|
||||
|
|
@ -207,6 +251,23 @@ export default function PlansPage() {
|
|||
>
|
||||
{plan.status === 'ACTIVE' ? t('plans.deactivateVersion') : t('plans.activate')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleEditClick(plan)}
|
||||
className={styles.btnSecondary}
|
||||
id={`btn-edit-${plan.id}`}
|
||||
>
|
||||
{t('plans.editPlan')}
|
||||
</button>
|
||||
{plan.status === 'DRAFT' && (
|
||||
<button
|
||||
onClick={() => handleDeletePlan(plan.id)}
|
||||
className={styles.btnSecondary}
|
||||
style={{ borderColor: 'hsl(0, 75%, 60%)', color: 'hsl(0, 75%, 60%)' }}
|
||||
id={`btn-delete-${plan.id}`}
|
||||
>
|
||||
{t('plans.deletePlan')}
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Link href={`/plans/${plan.id}/rules`} className={styles.btnSecondary} id={`btn-rules-${plan.id}`}>
|
||||
|
|
@ -224,7 +285,7 @@ export default function PlansPage() {
|
|||
{showModal && (
|
||||
<div className={styles.modalOverlay} role="dialog">
|
||||
<div className={styles.modal}>
|
||||
<h2 className={styles.modalTitle}>Crear {t('plans.newPlan')}</h2>
|
||||
<h2 className={styles.modalTitle}>{editPlanId ? t('plans.modalEditTitle') : t('plans.modalCreateTitle')}</h2>
|
||||
<form onSubmit={handleCreatePlan} className={styles.form}>
|
||||
{error && <div className={styles.errorText}>{error}</div>}
|
||||
|
||||
|
|
@ -332,7 +393,7 @@ export default function PlansPage() {
|
|||
</div>
|
||||
|
||||
<div className={styles.formActions}>
|
||||
<button type="button" onClick={() => setShowModal(false)} className={styles.btnSecondary}>
|
||||
<button type="button" onClick={handleCloseModal} className={styles.btnSecondary}>
|
||||
{t('plans.modalCancel')}
|
||||
</button>
|
||||
<button type="submit" className={styles.btnPrimary} id="btn-save-plan">
|
||||
|
|
|
|||
|
|
@ -149,7 +149,12 @@
|
|||
},
|
||||
"errorFind": "Could not find the plan.",
|
||||
"errorLoad": "Error loading plan information.",
|
||||
"modalValidityEnd": "End Validity (Optional)"
|
||||
"modalValidityEnd": "End Validity (Optional)",
|
||||
"editPlan": "Edit Details",
|
||||
"deletePlan": "Delete Plan",
|
||||
"confirmDelete": "Are you sure you want to delete this plan? This action cannot be undone.",
|
||||
"modalEditTitle": "Edit Plan",
|
||||
"modalCreateTitle": "Create New Plan"
|
||||
},
|
||||
"rules": {
|
||||
"back": "Back to Plans",
|
||||
|
|
|
|||
|
|
@ -149,7 +149,12 @@
|
|||
},
|
||||
"errorFind": "No se pudo encontrar el plan.",
|
||||
"errorLoad": "Error al cargar la información del plan.",
|
||||
"modalValidityEnd": "Fin de Vigencia (Opcional)"
|
||||
"modalValidityEnd": "Fin de Vigencia (Opcional)",
|
||||
"editPlan": "Editar Detalles",
|
||||
"deletePlan": "Eliminar Plan",
|
||||
"confirmDelete": "¿Está seguro de que desea eliminar este plan? Esta acción no se puede deshacer.",
|
||||
"modalEditTitle": "Editar Plan",
|
||||
"modalCreateTitle": "Crear Nuevo Plan"
|
||||
},
|
||||
"rules": {
|
||||
"back": "Volver a Planes",
|
||||
|
|
|
|||
Loading…
Reference in a new issue