Retrait des disponibilités et des limites de temps dans la journée
Disponibilités déclarées supprimées (table `availability`, migration 0022, API, éditeur de la fiche bénévole, badge de couverture du panneau d'affectation, colonnes des statistiques) ainsi que les règles OUTSIDE_AVAILABILITY, DAILY_HOURS_EXCEEDED et INSUFFICIENT_REST, les malus de disponibilité et de repos du solveur et le filtre « Surchargés » de la vue bénévoles. Seul le chevauchement reste contrôlé. La case « je suis disponible » du formulaire public est conservée. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
f671bb85bf
commit
d722a81a87
@@ -6,33 +6,24 @@ import {
|
||||
useAssignments,
|
||||
useCreateAssignment,
|
||||
useDeleteAssignment,
|
||||
useEditionAvailability,
|
||||
usePositions,
|
||||
useShifts,
|
||||
useSolverSuggestions,
|
||||
useValidateAssignment,
|
||||
useVolunteers,
|
||||
} from '@/api/hooks';
|
||||
import type {
|
||||
AvailabilityRead,
|
||||
MatrixShift,
|
||||
PositionCategory,
|
||||
VolunteerRead,
|
||||
} from '@/api/schema';
|
||||
import type { MatrixShift, PositionCategory, VolunteerRead } from '@/api/schema';
|
||||
import { Btn } from '@/components/ui';
|
||||
import { Loading } from '@/components/state';
|
||||
import {
|
||||
artistPerformanceWindow,
|
||||
availabilityCoverage,
|
||||
buildEffectiveCompanionIds,
|
||||
candidateChanceScore,
|
||||
categoryLabel,
|
||||
foldAccents,
|
||||
hoursByVolunteer,
|
||||
uncoveredMinutes,
|
||||
violationText,
|
||||
windowsOverlap,
|
||||
type Coverage,
|
||||
} from '@/lib/planning';
|
||||
import { hourRangeLabel, hoursBetween, shortDayLabel } from '@/lib/time';
|
||||
import { t } from '@/i18n';
|
||||
@@ -63,7 +54,6 @@ export function AssignmentPanel({
|
||||
const shiftAssignments = useAssignments({ shiftId: shift.shift_id });
|
||||
const shifts = useShifts();
|
||||
const positions = usePositions();
|
||||
const availability = useEditionAvailability();
|
||||
const artists = useArtists();
|
||||
const solverSuggestions = useSolverSuggestions();
|
||||
const deleteAssignment = useDeleteAssignment();
|
||||
@@ -75,27 +65,6 @@ export function AssignmentPanel({
|
||||
return entry?.volunteer_ids ?? [];
|
||||
}, [solverSuggestions.data, shift.shift_id]);
|
||||
|
||||
const windowsByVolunteer = useMemo(() => {
|
||||
const byVol = new Map<string, AvailabilityRead[]>();
|
||||
for (const w of availability.data ?? []) {
|
||||
const list = byVol.get(w.volunteer_id) ?? [];
|
||||
list.push(w);
|
||||
byVol.set(w.volunteer_id, list);
|
||||
}
|
||||
return byVol;
|
||||
}, [availability.data]);
|
||||
|
||||
const coverageByVolunteer = useMemo(() => {
|
||||
const map = new Map<string, Coverage>();
|
||||
for (const v of volunteers.data ?? []) {
|
||||
map.set(
|
||||
v.id,
|
||||
availabilityCoverage(windowsByVolunteer.get(v.id) ?? [], shift.starts_at, shift.ends_at),
|
||||
);
|
||||
}
|
||||
return map;
|
||||
}, [windowsByVolunteer, volunteers.data, shift.starts_at, shift.ends_at]);
|
||||
|
||||
const artistsById = useMemo(
|
||||
() => new Map((artists.data ?? []).map((a) => [a.id, a])),
|
||||
[artists.data],
|
||||
@@ -166,8 +135,7 @@ export function AssignmentPanel({
|
||||
}, [volunteers.data, artistsById, shift, positionId, assigned, effectiveCompanionIds]);
|
||||
|
||||
// Score de chance de chaque candidat pour CE créneau : vœu de poste,
|
||||
// disponibilité (dont conflit avec un artiste qu'il veut voir), compagnons
|
||||
// souhaités déjà présents, charge déjà assignée — c'est ce qui classe la
|
||||
// conflit avec un artiste qu'il veut voir, compagnons souhaités déjà présents, charge déjà assignée — c'est ce qui classe la
|
||||
// liste, pas juste « le moins chargé ».
|
||||
const scoreByVolunteer = useMemo(() => {
|
||||
const map = new Map<string, number>();
|
||||
@@ -177,11 +145,6 @@ export function AssignmentPanel({
|
||||
v.id,
|
||||
candidateChanceScore({
|
||||
wishesPosition: details?.wishesPosition ?? false,
|
||||
uncoveredMinutes: uncoveredMinutes(
|
||||
windowsByVolunteer.get(v.id) ?? [],
|
||||
shift.starts_at,
|
||||
shift.ends_at,
|
||||
),
|
||||
conflictsWishedArtist: details?.conflictsWishedArtist ?? false,
|
||||
companionMatches: details?.companionMatches ?? 0,
|
||||
assignedHours: hours.get(v.id) ?? 0,
|
||||
@@ -189,7 +152,7 @@ export function AssignmentPanel({
|
||||
);
|
||||
}
|
||||
return map;
|
||||
}, [volunteers.data, detailsByVolunteer, windowsByVolunteer, hours, shift]);
|
||||
}, [volunteers.data, detailsByVolunteer, hours]);
|
||||
|
||||
const needle = foldAccents(search.trim());
|
||||
|
||||
@@ -319,7 +282,6 @@ export function AssignmentPanel({
|
||||
shiftId={shift.shift_id}
|
||||
hours={round(hours.get(volunteer.id) ?? 0)}
|
||||
slots={slotsByVolunteer.get(volunteer.id) ?? 0}
|
||||
coverage={coverageByVolunteer.get(volunteer.id) ?? 'none'}
|
||||
wishesPosition={detailsByVolunteer.get(volunteer.id)?.wishesPosition ?? false}
|
||||
conflictsWishedArtist={
|
||||
detailsByVolunteer.get(volunteer.id)?.conflictsWishedArtist ?? false
|
||||
@@ -334,18 +296,11 @@ export function AssignmentPanel({
|
||||
);
|
||||
}
|
||||
|
||||
const COVERAGE_STYLE: Record<Coverage, { cls: string; key: string }> = {
|
||||
full: { cls: 'bg-green text-white', key: 'grille.panel.coverage.full' },
|
||||
partial: { cls: 'bg-yellow-soft', key: 'grille.panel.coverage.partial' },
|
||||
none: { cls: 'bg-sand-dark', key: 'grille.panel.coverage.none' },
|
||||
};
|
||||
|
||||
function CandidateRow({
|
||||
volunteer,
|
||||
shiftId,
|
||||
hours,
|
||||
slots,
|
||||
coverage,
|
||||
wishesPosition,
|
||||
conflictsWishedArtist,
|
||||
companionMatches,
|
||||
@@ -356,7 +311,6 @@ function CandidateRow({
|
||||
shiftId: string;
|
||||
hours: number;
|
||||
slots: number;
|
||||
coverage: Coverage;
|
||||
wishesPosition: boolean;
|
||||
conflictsWishedArtist: boolean;
|
||||
companionMatches: number;
|
||||
@@ -411,11 +365,6 @@ function CandidateRow({
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-[5px] px-[7px] pb-[7px]">
|
||||
<span
|
||||
className={`inline-block border-[1.5px] border-ink px-[6px] py-[3px] font-display text-[8.5px] font-extrabold uppercase tracking-wide ${COVERAGE_STYLE[coverage].cls}`}
|
||||
>
|
||||
{t(COVERAGE_STYLE[coverage].key)}
|
||||
</span>
|
||||
{wishesPosition ? (
|
||||
<span className="inline-block border-[1.5px] border-ink bg-yellow px-[6px] py-[3px] font-display text-[8.5px] font-extrabold uppercase tracking-wide">
|
||||
{t('grille.panel.wishesPosition')}
|
||||
|
||||
Reference in New Issue
Block a user