feat(appointments): toolbar personnel filter (پرسنل را انتخاب کنید...)
Adds staffUuid to AppointmentFilters and a toolbar staff select that filters the loaded day's rows client-side, matching appointments-table.pdf. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -40,6 +40,10 @@ describe('applyAppointmentFilters', () => {
|
|||||||
expect(applyAppointmentFilters(items, { ...EMPTY_FILTERS, statuses: ['cancelled'] })).toHaveLength(1);
|
expect(applyAppointmentFilters(items, { ...EMPTY_FILTERS, statuses: ['cancelled'] })).toHaveLength(1);
|
||||||
expect(applyAppointmentFilters(items, { ...EMPTY_FILTERS, statuses: ['salon', 'completed'] })).toHaveLength(2);
|
expect(applyAppointmentFilters(items, { ...EMPTY_FILTERS, statuses: ['salon', 'completed'] })).toHaveLength(2);
|
||||||
expect(applyAppointmentFilters(items, { ...EMPTY_FILTERS, gender: 'female' })).toHaveLength(2);
|
expect(applyAppointmentFilters(items, { ...EMPTY_FILTERS, gender: 'female' })).toHaveLength(2);
|
||||||
|
expect(applyAppointmentFilters(
|
||||||
|
items.map((a, i) => ({ ...a, staff: i === 0 ? { uuid: 'st1', full_name: 'x' } : null })),
|
||||||
|
{ ...EMPTY_FILTERS, staffUuid: 'st1' },
|
||||||
|
)).toHaveLength(1);
|
||||||
expect(applyAppointmentFilters(items, EMPTY_FILTERS)).toHaveLength(3);
|
expect(applyAppointmentFilters(items, EMPTY_FILTERS)).toHaveLength(3);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,12 +13,14 @@ export interface AppointmentFilters {
|
|||||||
nationalCode: string;
|
nationalCode: string;
|
||||||
sectionUuid: string;
|
sectionUuid: string;
|
||||||
itemUuid: string;
|
itemUuid: string;
|
||||||
|
/** toolbar «پرسنل را انتخاب کنید...» select — not part of the modal */
|
||||||
|
staffUuid: string;
|
||||||
statuses: string[];
|
statuses: string[];
|
||||||
gender: 'female' | 'male' | 'both';
|
gender: 'female' | 'male' | 'both';
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EMPTY_FILTERS: AppointmentFilters = {
|
export const EMPTY_FILTERS: AppointmentFilters = {
|
||||||
name: '', nationalCode: '', sectionUuid: '', itemUuid: '', statuses: [], gender: 'both',
|
name: '', nationalCode: '', sectionUuid: '', itemUuid: '', staffUuid: '', statuses: [], gender: 'both',
|
||||||
};
|
};
|
||||||
|
|
||||||
// design's 6 checkboxes; لغو شده covers both cancel reasons
|
// design's 6 checkboxes; لغو شده covers both cancel reasons
|
||||||
@@ -38,6 +40,7 @@ export function applyAppointmentFilters(items: Appointment[], f: AppointmentFilt
|
|||||||
if (f.nationalCode && !(a.patient_national_code ?? '').includes(f.nationalCode)) return false;
|
if (f.nationalCode && !(a.patient_national_code ?? '').includes(f.nationalCode)) return false;
|
||||||
if (f.sectionUuid && a.service_section?.uuid !== f.sectionUuid) return false;
|
if (f.sectionUuid && a.service_section?.uuid !== f.sectionUuid) return false;
|
||||||
if (f.itemUuid && a.service_item?.uuid !== f.itemUuid) return false;
|
if (f.itemUuid && a.service_item?.uuid !== f.itemUuid) return false;
|
||||||
|
if (f.staffUuid && a.staff?.uuid !== f.staffUuid) return false;
|
||||||
if (f.statuses.length) {
|
if (f.statuses.length) {
|
||||||
const matches = f.statuses.some(s =>
|
const matches = f.statuses.some(s =>
|
||||||
s === 'cancelled' ? a.status.startsWith('cancelled') : a.status === s);
|
s === 'cancelled' ? a.status.startsWith('cancelled') : a.status === s);
|
||||||
|
|||||||
@@ -712,6 +712,9 @@ export default function AppointmentsPage() {
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Personnel filter (Figma «پرسنل را انتخاب کنید...») */}
|
||||||
|
<StaffFilterSelect value={filters.staffUuid} onChange={(v) => setFilters(f => ({ ...f, staffUuid: v }))} />
|
||||||
|
|
||||||
{/* Filters (Figma فیلترها) */}
|
{/* Filters (Figma فیلترها) */}
|
||||||
<button
|
<button
|
||||||
aria-label="فیلترها"
|
aria-label="فیلترها"
|
||||||
@@ -846,6 +849,20 @@ export default function AppointmentsPage() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Toolbar staff filter — filters the loaded day's table rows by پرسنل. */
|
||||||
|
function StaffFilterSelect({ value, onChange }: { value: string; onChange: (v: string) => void }) {
|
||||||
|
const staffQ = useQuery<ApiResponse<{ uuid: string; full_name: string }[]>>({
|
||||||
|
queryKey: ['staff-list'], queryFn: () => api.get('/api/v1/staff'),
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<select aria-label="پرسنل" value={value} onChange={e => onChange(e.target.value)}
|
||||||
|
style={{ height: 32, borderRadius: 'var(--r-sm)', border: '1px solid var(--border)', background: 'var(--surface)', fontSize: 12.5, fontFamily: 'inherit', padding: '0 10px', minWidth: 170 }}>
|
||||||
|
<option value="">پرسنل را انتخاب کنید...</option>
|
||||||
|
{(staffQ.data?.data ?? []).map(s => <option key={s.uuid} value={s.uuid}>{s.full_name}</option>)}
|
||||||
|
</select>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function tabStyle(active: boolean): React.CSSProperties {
|
function tabStyle(active: boolean): React.CSSProperties {
|
||||||
return {
|
return {
|
||||||
padding: '6px 14px', borderRadius: 'var(--r-sm)', fontSize: 13, fontWeight: 600,
|
padding: '6px 14px', borderRadius: 'var(--r-sm)', fontSize: 13, fontWeight: 600,
|
||||||
|
|||||||
Reference in New Issue
Block a user