feat(f6.9): consolidate act and report documents and simplify follow-up
Android CI / RC / Android · lint, tests, debug APK, release compile (push) Successful in 3m24s
DH V2 CI / API · typecheck, tests, build (push) Successful in 40s
DH V2 CI / WEB · typecheck, build (push) Successful in 18s
Production dependency audit / API · production dependencies (push) Successful in 8s
Production dependency audit / WEB · production dependencies (push) Successful in 9s
DH V2 CI / Docker / scripts contract (push) Successful in 1m22s
Android CI / RC / Android · lint, tests, debug APK, release compile (push) Successful in 3m24s
DH V2 CI / API · typecheck, tests, build (push) Successful in 40s
DH V2 CI / WEB · typecheck, build (push) Successful in 18s
Production dependency audit / API · production dependencies (push) Successful in 8s
Production dependency audit / WEB · production dependencies (push) Successful in 9s
DH V2 CI / Docker / scripts contract (push) Successful in 1m22s
This commit is contained in:
@@ -1,20 +1,31 @@
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import type { FormEvent } from 'react';
|
||||
import { Link, useParams } from 'react-router';
|
||||
import { Alert, LoadingBlock, errorMessage } from '../components/Feedback';
|
||||
import { addActCompanyResponse, actCompanyResponseContentUrl, getActAdministration, setActResponseDeadline } from '../lib/api';
|
||||
import { actCompanyResponseContentUrl, getActAdministration } from '../lib/api';
|
||||
import type { ActAdministrationDetail } from '../lib/api';
|
||||
import { getInspectionActF4 } from '../lib/inspectionActF4Api';
|
||||
import { formatDate } from '../lib/format';
|
||||
|
||||
export function ActAdministrationDetailPage(){
|
||||
const {actId=''}=useParams(); const [data,setData]=useState<ActAdministrationDetail|null>(null); const [error,setError]=useState(''); const [busy,setBusy]=useState(false); const [due,setDue]=useState(''); const [reason,setReason]=useState('Plazo administrativo otorgado a la empresa'); const [received,setReceived]=useState(new Date().toISOString().slice(0,10)); const [details,setDetails]=useState(''); const [commitment,setCommitment]=useState(''); const [file,setFile]=useState<File|undefined>();
|
||||
const load=()=>getActAdministration(actId).then(setData).catch(e=>setError(errorMessage(e))); useEffect(()=>{void load()},[actId]);
|
||||
const deadline=async(e:FormEvent)=>{e.preventDefault();setBusy(true);setError('');try{await setActResponseDeadline(actId,{responseDueOn:due,reason});setDue('');await load()}catch(err){setError(errorMessage(err))}finally{setBusy(false)}};
|
||||
const response=async(e:FormEvent)=>{e.preventDefault();setBusy(true);setError('');try{await addActCompanyResponse(actId,{receivedOn:received,details:details||undefined,committedCorrectionOn:commitment||undefined,file});setDetails('');setCommitment('');setFile(undefined);await load()}catch(err){setError(errorMessage(err))}finally{setBusy(false)}};
|
||||
if(!data&&!error)return <LoadingBlock label="Cargando expediente administrativo…"/>;
|
||||
return <section>{error&&<Alert>{error}</Alert>}{data&&<><div className="page-heading"><div><span className="eyebrow">EXPEDIENTE ADMINISTRATIVO</span><h1>{data.act.actCode}</h1><p>{data.act.areaName??'Área sin asignar'} · {data.act.companyName??'Empresa sin asignar'} · {data.act.openFindingCount} hallazgos abiertos</p></div><Link className="button secondary" to={`/inspecciones/actas/${data.act.actId}`}>Ver Acta</Link></div>
|
||||
<div className="detail-grid"><div className="card"><h2>Plazo de respuesta</h2><p>El plazo aplica al conjunto completo de hallazgos del Acta.</p><form className="form-grid" onSubmit={deadline}><label><span>Vencimiento *</span><input type="date" required value={due} onChange={e=>setDue(e.target.value)}/></label><label className="full"><span>Motivo *</span><textarea required minLength={3} value={reason} onChange={e=>setReason(e.target.value)}/></label><button className="button primary" disabled={busy}>Registrar nuevo plazo</button></form>{data.deadlines.length>0&&<div className="stack-list">{data.deadlines.map(d=><div key={d.id}><strong>{formatDate(d.responseDueOn)}</strong><small>{d.reason}</small></div>)}</div>}</div>
|
||||
<div className="card"><h2>Respuesta de la empresa</h2><form className="form-grid" onSubmit={response}><label><span>Recibida el *</span><input type="date" required value={received} onChange={e=>setReceived(e.target.value)}/></label><label><span>Fecha comprometida</span><input type="date" value={commitment} onChange={e=>setCommitment(e.target.value)}/></label><label className="full"><span>Detalle</span><textarea value={details} onChange={e=>setDetails(e.target.value)} placeholder="Resumen de la presentación de la empresa"/></label><label className="full"><span>PDF presentado</span><input type="file" accept="application/pdf,.pdf" onChange={e=>setFile(e.target.files?.[0])}/></label><button className="button primary" disabled={busy}>Registrar respuesta</button></form>{data.responses.length>0&&<div className="stack-list">{data.responses.map(r=><div key={r.id}><strong>{formatDate(r.receivedOn)}</strong><small>{r.details??'Sin detalle'}{r.committedCorrectionOn?` · compromiso ${formatDate(r.committedCorrectionOn)}`:''}</small>{r.originalName&&<a className="text-link" href={actCompanyResponseContentUrl(r.id)} target="_blank" rel="noreferrer">Abrir PDF</a>}</div>)}</div>}</div></div>
|
||||
<div className="card"><h2>Hallazgos del Acta</h2><div className="table-scroll"><table><thead><tr><th>Código</th><th>Hallazgo</th><th>Estado</th><th>Próximo control</th></tr></thead><tbody>{data.findings.map(f=><tr key={f.id}><td><Link className="text-link" to={`/hallazgos/${f.id}`}>{f.code}</Link></td><td>{f.title}</td><td>{f.status}</td><td>{f.nextControlOn?formatDate(f.nextControlOn):'Sin programar'}</td></tr>)}</tbody></table></div></div></>}</section>;
|
||||
export function ActAdministrationDetailPage() {
|
||||
const { actId = '' } = useParams();
|
||||
const [data, setData] = useState<ActAdministrationDetail | null>(null);
|
||||
const [reportId, setReportId] = useState<string | null>(null);
|
||||
const [error, setError] = useState('');
|
||||
useEffect(() => {
|
||||
Promise.all([getActAdministration(actId), getInspectionActF4(actId)])
|
||||
.then(([detail, act]) => { setData(detail); setReportId(act.report?.id ?? null); })
|
||||
.catch((requestError) => setError(errorMessage(requestError)));
|
||||
}, [actId]);
|
||||
if (!data && !error) return <LoadingBlock label="Cargando histórico…" />;
|
||||
return <section>
|
||||
{error && <Alert>{error}</Alert>}
|
||||
{data && <>
|
||||
<div className="page-heading"><div><span className="eyebrow">HISTÓRICO ADMINISTRATIVO</span><h1>{data.act.actCode}</h1><p>Registro previo de plazos y respuestas asociados al Acta.</p></div><Link className="button secondary" to={`/inspecciones/actas/${actId}`}>Ver Acta</Link></div>
|
||||
{reportId && <p>Las nuevas respuestas y verificaciones se registran en el <Link to={`/informes/${reportId}`}>Informe relacionado</Link>.</p>}
|
||||
<section className="panel"><h2>Antecedentes conservados</h2>
|
||||
{!data.deadlines.length && !data.responses.length && <p>No hay antecedentes administrativos anteriores.</p>}
|
||||
{[...data.deadlines.map((item) => ({ id: item.id, date: item.createdAt, title: 'Plazo registrado', description: `${formatDate(item.responseDueOn)} · ${item.reason}`, fileId: null as string | null, fileName: null as string | null })), ...data.responses.map((item) => ({ id: item.id, date: item.receivedOn, title: 'Respuesta de la empresa', description: item.details ?? 'Sin descripción', fileId: item.id, fileName: item.originalName }))].sort((a, b) => b.date.localeCompare(a.date)).map((item) => <div key={item.id} className="act-signer-row"><div><strong>{item.title}</strong><small>{formatDate(item.date)}</small><p>{item.description}</p></div>{item.fileName && item.fileId && <a className="button secondary" href={actCompanyResponseContentUrl(item.fileId)} target="_blank" rel="noreferrer">Abrir {item.fileName}</a>}</div>)}
|
||||
</section>
|
||||
</>}
|
||||
</section>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user