Files
dh-inspeccion-v2/web-v2/src/pages/SurveyCampaignsPage.tsx
T

82 lines
5.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { SearchableSelect } from '../components/SearchableSelect';
import { useEffect, useState } from 'react';
import type { FormEvent } from 'react';
import { Link, useSearchParams } from 'react-router';
import { PermissionGate } from '../auth/PermissionGate';
import { Alert, EmptyState, LoadingBlock, errorMessage } from '../components/Feedback';
import { Icon } from '../components/Icon';
import {
SURVEY_CAMPAIGN_STATUSES,
surveyCampaignStatusLabel,
surveyStatusClass,
} from '../features/surveys/surveyPresentation';
import { listSurveyCampaigns } from '../lib/api';
import type {
PageMeta,
SurveyCampaignListItem,
SurveyCampaignStatus,
} from '../lib/api';
import { formatDate } from '../lib/format';
export function SurveyCampaignsPage() {
const [urlParams, setUrlParams] = useSearchParams();
const [campaigns, setCampaigns] = useState<SurveyCampaignListItem[]>([]);
const [meta, setMeta] = useState<PageMeta>({ page: 1, pageSize: 25, total: 0, totalPages: 0 });
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
const [draftSearch, setDraftSearch] = useState(urlParams.get('search') ?? '');
const search = urlParams.get('search') ?? '';
const rawStatus = urlParams.get('status') ?? '';
const status = SURVEY_CAMPAIGN_STATUSES.some((item) => item.value === rawStatus)
? rawStatus as SurveyCampaignStatus
: '';
const page = Math.max(1, Number(urlParams.get('page') ?? 1) || 1);
useEffect(() => {
setLoading(true);
setError('');
listSurveyCampaigns({ page, pageSize: 25, search, status })
.then((response) => {
setCampaigns(response.data);
setMeta(response.meta);
})
.catch((requestError) => setError(errorMessage(requestError)))
.finally(() => setLoading(false));
}, [page, search, status]);
const updateFilter = (key: string, value: string) => {
const next = new URLSearchParams(urlParams);
value ? next.set(key, value) : next.delete(key);
next.delete('page');
setUrlParams(next);
};
const applySearch = (event: FormEvent) => {
event.preventDefault();
updateFilter('search', draftSearch.trim());
};
const setPage = (value: number) => {
const next = new URLSearchParams(urlParams);
value > 1 ? next.set('page', String(value)) : next.delete('page');
setUrlParams(next);
};
return <section>
<div className="page-heading">
<div><span className="eyebrow">OPERACIÓN DE CAMPO</span><h1>Relevamientos</h1><p>Campañas planificadas sobre registros existentes de los inventarios.</p></div>
<PermissionGate permission="surveys.manage"><Link className="button primary" to="/relevamiento/nuevo"><Icon name="plus" />Nueva campaña</Link></PermissionGate>
</div>
<div className="temporal-notice"><Icon name="clipboard" /><p><strong>Inventarios organizados por empresa.</strong> Las campañas organizan qué registros relevar, quién los atiende y su avance; no crean un inventario paralelo.</p></div>
<form className="toolbar survey-toolbar" onSubmit={applySearch}>
<label className="search-field"><Icon name="search" /><input value={draftSearch} onChange={(event) => setDraftSearch(event.target.value)} placeholder="Buscar por código o nombre" /><button>Buscar</button></label>
<label className="select-field"><span>Estado</span><SearchableSelect value={status} onChange={(event) => updateFilter('status', event.target.value)}><option value="">Todos</option>{SURVEY_CAMPAIGN_STATUSES.map((item) => <option key={item.value} value={item.value}>{item.label}</option>)}</SearchableSelect></label>
</form>
{error && <Alert>{error}</Alert>}
{loading ? <LoadingBlock label="Cargando campañas…" /> : campaigns.length === 0 ? <EmptyState title="Sin campañas" text="Creá una campaña para planificar el próximo relevamiento de los inventarios." /> : <div className="table-panel"><div className="table-summary"><strong>{meta.total} campaña{meta.total === 1 ? '' : 's'}</strong><span>Página {page} de {Math.max(meta.totalPages, 1)}</span></div><div className="table-scroll"><table><thead><tr><th>Campaña</th><th>Estado</th><th>Alcance</th><th>Coordinación</th><th>Fechas previstas</th><th>Avance</th><th /></tr></thead><tbody>{campaigns.map((campaign) => <tr key={campaign.id}><td><div className="asset-cell"><span className="asset-symbol"><Icon name="clipboard" size={17} /></span><div><strong>{campaign.name}</strong><small>{campaign.code}</small></div></div></td><td><span className={`status-badge ${surveyStatusClass(campaign.status)}`}>{surveyCampaignStatusLabel(campaign.status)}</span></td><td>{campaign.scopeAsset ? <Link className="text-link" to={`/inventarios/${campaign.scopeAsset.id}`}>{campaign.scopeAsset.name}</Link> : <span className="muted">Todos los inventarios</span>}</td><td>{campaign.coordinator ? `${campaign.coordinator.firstName} ${campaign.coordinator.lastName}` : 'Sin asignar'}</td><td><span className="survey-date-range">{formatDate(campaign.plannedStartAt)}<small>hasta {formatDate(campaign.plannedEndAt)}</small></span></td><td><div className="survey-progress"><strong>{campaign.completedCount + campaign.skippedCount}/{campaign.targetCount}</strong><span><i style={{ width: `${campaign.targetCount ? ((campaign.completedCount + campaign.skippedCount) / campaign.targetCount) * 100 : 0}%` }} /></span></div></td><td className="action-cell"><Link className="icon-button" to={`/relevamiento/${campaign.id}`} aria-label={`Abrir ${campaign.name}`}><Icon name="chevron" /></Link></td></tr>)}</tbody></table></div><div className="pagination"><button className="button secondary" disabled={page <= 1} onClick={() => setPage(page - 1)}>Anterior</button><span>{meta.total ? `${(page - 1) * meta.pageSize + 1}${Math.min(page * meta.pageSize, meta.total)} de ${meta.total}` : '0 resultados'}</span><button className="button secondary" disabled={page >= meta.totalPages} onClick={() => setPage(page + 1)}>Siguiente</button></div></div>}
</section>;
}