erp-construccion-frontend-v2/web/src/components/contracts/PartidaModal.tsx
Adrian Flores Cortes a03bed842f [REMEDIATION] feat: Frontend remediation - auth, finance, contracts, session management
Add auth components, finance pages/hooks/services, contract components.
Enhance LoginPage, AdminLayout, hooks. Remove legacy apiClient.
Add mock data services for development. Addresses frontend gaps.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-05 23:18:22 -06:00

164 lines
4.9 KiB
TypeScript

/**
* PartidaModal - Modal para crear/editar partidas de contrato
*/
import { useState } from 'react';
import {
useCreateContractPartida,
useUpdateContractPartida,
} from '../../hooks/useContracts';
import type {
ContractPartida,
CreateContractPartidaDto,
} from '../../types/contracts.types';
import {
Modal,
ModalFooter,
TextInput,
FormGroup,
} from '../common';
interface PartidaModalProps {
contractId: string;
partida: ContractPartida | null;
onClose: () => void;
}
export function PartidaModal({ contractId, partida, onClose }: PartidaModalProps) {
const createMutation = useCreateContractPartida();
const updateMutation = useUpdateContractPartida();
const [formData, setFormData] = useState<CreateContractPartidaDto & { conceptoCode?: string; conceptoDescription?: string }>({
conceptoId: partida?.conceptoId || '',
conceptoCode: partida?.conceptoCode || '',
conceptoDescription: partida?.conceptoDescription || '',
quantity: partida?.quantity || 0,
unitPrice: partida?.unitPrice || 0,
});
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const payload: CreateContractPartidaDto = {
conceptoId: formData.conceptoId,
quantity: formData.quantity,
unitPrice: formData.unitPrice,
};
if (partida) {
await updateMutation.mutateAsync({
contractId,
partidaId: partida.id,
data: {
quantity: formData.quantity,
unitPrice: formData.unitPrice,
},
});
} else {
await createMutation.mutateAsync({ contractId, data: payload });
}
onClose();
};
const update = <K extends keyof typeof formData>(field: K, value: typeof formData[K]) => {
setFormData({ ...formData, [field]: value });
};
const isLoading = createMutation.isPending || updateMutation.isPending;
const total = formData.quantity * formData.unitPrice;
const formatCurrency = (value: number) => {
return new Intl.NumberFormat('es-MX', {
style: 'currency',
currency: 'MXN',
}).format(value);
};
return (
<Modal
isOpen={true}
onClose={onClose}
title={partida ? 'Editar Partida' : 'Nueva Partida'}
size="md"
footer={
<ModalFooter>
<button
type="button"
className="px-4 py-2 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700"
onClick={onClose}
>
Cancelar
</button>
<button
type="submit"
form="partida-form"
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50"
disabled={isLoading}
>
{isLoading ? 'Guardando...' : partida ? 'Guardar Cambios' : 'Agregar Partida'}
</button>
</ModalFooter>
}
>
<form id="partida-form" onSubmit={handleSubmit} className="space-y-4">
{/* Concepto Selection - In a real app this would be a searchable select */}
<TextInput
label="ID del Concepto"
required
value={formData.conceptoId}
onChange={(e) => update('conceptoId', e.target.value)}
placeholder="UUID del concepto"
disabled={!!partida}
/>
{partida && (
<div className="bg-gray-50 dark:bg-gray-700/50 rounded-lg p-3">
<p className="text-sm text-gray-500 dark:text-gray-400">Concepto</p>
<p className="font-medium text-gray-900 dark:text-gray-100">
{partida.conceptoCode || 'N/A'}
</p>
<p className="text-sm text-gray-600 dark:text-gray-400">
{partida.conceptoDescription || '-'}
</p>
</div>
)}
<FormGroup cols={2}>
<TextInput
label="Cantidad"
type="number"
required
value={formData.quantity.toString()}
onChange={(e) => update('quantity', parseFloat(e.target.value) || 0)}
placeholder="0.00"
min="0"
step="0.01"
/>
<TextInput
label="Precio Unitario"
type="number"
required
value={formData.unitPrice.toString()}
onChange={(e) => update('unitPrice', parseFloat(e.target.value) || 0)}
placeholder="0.00"
min="0"
step="0.01"
/>
</FormGroup>
{/* Total Calculated */}
<div className="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-4">
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">
Total:
</span>
<span className="text-lg font-bold text-blue-600 dark:text-blue-400">
{formatCurrency(total)}
</span>
</div>
</div>
</form>
</Modal>
);
}