mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
fix: close file picker on outside click and modal dismiss
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
// FE-PLANNER-RESMODAL-001 to FE-PLANNER-RESMODAL-093
|
||||
// FE-PLANNER-RESMODAL-001 to FE-PLANNER-RESMODAL-095
|
||||
import { render, screen, waitFor, fireEvent, within } from '../../../tests/helpers/render';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { http, HttpResponse } from 'msw';
|
||||
@@ -558,6 +558,35 @@ describe('ReservationModal', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('FE-PLANNER-RESMODAL-094: an outside pointer closes the file picker while an inside pointer keeps it open', async () => {
|
||||
const res = buildReservation({ id: 5 });
|
||||
const unattachedFile = buildTripFile({ id: 99, original_name: 'invoice.pdf' });
|
||||
|
||||
render(<ReservationModal {...defaultProps} reservation={res} files={[unattachedFile]} />);
|
||||
await userEvent.click(screen.getByRole('button', { name: /Link existing file/i }));
|
||||
|
||||
const pickerItem = screen.getByText('invoice.pdf');
|
||||
fireEvent.pointerDown(pickerItem);
|
||||
expect(screen.getByText('invoice.pdf')).toBeInTheDocument();
|
||||
|
||||
fireEvent.pointerDown(document.body);
|
||||
expect(screen.queryByText('invoice.pdf')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('FE-PLANNER-RESMODAL-095: closing and reopening the modal resets the file picker', async () => {
|
||||
const res = buildReservation({ id: 5 });
|
||||
const unattachedFile = buildTripFile({ id: 99, original_name: 'invoice.pdf' });
|
||||
const { rerender } = render(<ReservationModal {...defaultProps} reservation={res} files={[unattachedFile]} />);
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: /Link existing file/i }));
|
||||
expect(screen.getByText('invoice.pdf')).toBeInTheDocument();
|
||||
|
||||
rerender(<ReservationModal {...defaultProps} isOpen={false} reservation={res} files={[unattachedFile]} />);
|
||||
rerender(<ReservationModal {...defaultProps} reservation={res} files={[unattachedFile]} />);
|
||||
|
||||
expect(screen.queryByText('invoice.pdf')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('FE-PLANNER-RESMODAL-040: removing pending file removes it from list', async () => {
|
||||
render(<ReservationModal {...defaultProps} reservation={null} />);
|
||||
|
||||
|
||||
@@ -84,6 +84,20 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p
|
||||
const [linkedFileIds, setLinkedFileIds] = useState<number[]>([])
|
||||
// Travelers assigned to this booking (#1517) — seeded on open, persisted after the save resolves.
|
||||
const [travelerIds, setTravelerIds] = useState<Set<number>>(new Set())
|
||||
const filePickerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!showFilePicker) return
|
||||
const closeOnOutsidePointer = (event: PointerEvent) => {
|
||||
if (!filePickerRef.current?.contains(event.target as Node)) setShowFilePicker(false)
|
||||
}
|
||||
document.addEventListener('pointerdown', closeOnOutsidePointer)
|
||||
return () => document.removeEventListener('pointerdown', closeOnOutsidePointer)
|
||||
}, [showFilePicker])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) setShowFilePicker(false)
|
||||
}, [isOpen])
|
||||
|
||||
const assignmentOptions = useMemo(
|
||||
() => buildAssignmentOptions(days, assignments, t, locale),
|
||||
@@ -731,7 +745,7 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p
|
||||
{uploadingFile ? t('reservations.uploading') : t('reservations.attachFile')}
|
||||
</button>}
|
||||
{reservation?.id && files.filter(f => !f.deleted_at && !attachedFiles.some(af => af.id === f.id)).length > 0 && (
|
||||
<div style={{ position: 'relative' }}>
|
||||
<div ref={filePickerRef} style={{ position: 'relative' }}>
|
||||
<button type="button" onClick={() => setShowFilePicker(v => !v)} className="text-content-faint" style={{
|
||||
display: 'flex', alignItems: 'center', gap: 5, padding: '6px 10px',
|
||||
border: '1px dashed var(--border-primary)', borderRadius: 8, background: 'none',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// FE-PLANNER-TRANSMODAL-001 to FE-PLANNER-TRANSMODAL-061
|
||||
// FE-PLANNER-TRANSMODAL-001 to FE-PLANNER-TRANSMODAL-064
|
||||
import { render, screen, waitFor, fireEvent, within } from '../../../tests/helpers/render';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { http, HttpResponse } from 'msw';
|
||||
@@ -259,6 +259,35 @@ describe('TransportModal', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('FE-PLANNER-TRANSMODAL-063: an outside pointer closes the file picker while an inside pointer keeps it open', async () => {
|
||||
const res = buildReservation({ id: 5, type: 'flight' });
|
||||
const unattachedFile = buildTripFile({ id: 99, original_name: 'invoice.pdf' });
|
||||
|
||||
render(<TransportModal {...defaultProps} reservation={res} files={[unattachedFile]} />);
|
||||
await userEvent.click(screen.getByRole('button', { name: /Link existing file/i }));
|
||||
|
||||
const pickerItem = screen.getByText('invoice.pdf');
|
||||
fireEvent.pointerDown(pickerItem);
|
||||
expect(screen.getByText('invoice.pdf')).toBeInTheDocument();
|
||||
|
||||
fireEvent.pointerDown(document.body);
|
||||
expect(screen.queryByText('invoice.pdf')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('FE-PLANNER-TRANSMODAL-064: closing and reopening the modal resets the file picker', async () => {
|
||||
const res = buildReservation({ id: 5, type: 'flight' });
|
||||
const unattachedFile = buildTripFile({ id: 99, original_name: 'invoice.pdf' });
|
||||
const { rerender } = render(<TransportModal {...defaultProps} reservation={res} files={[unattachedFile]} />);
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: /Link existing file/i }));
|
||||
expect(screen.getByText('invoice.pdf')).toBeInTheDocument();
|
||||
|
||||
rerender(<TransportModal {...defaultProps} isOpen={false} reservation={res} files={[unattachedFile]} />);
|
||||
rerender(<TransportModal {...defaultProps} reservation={res} files={[unattachedFile]} />);
|
||||
|
||||
expect(screen.queryByText('invoice.pdf')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('FE-PLANNER-TRANSMODAL-022: removing pending file removes it from list', async () => {
|
||||
render(<TransportModal {...defaultProps} reservation={null} />);
|
||||
|
||||
|
||||
@@ -234,6 +234,20 @@ export function TransportModal({ isOpen, onClose, onSave, reservation, days, sel
|
||||
// Travelers assigned to this booking (#1517) — seeded on open, persisted after save.
|
||||
const [travelerIds, setTravelerIds] = useState<Set<number>>(new Set())
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
const filePickerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!showFilePicker) return
|
||||
const closeOnOutsidePointer = (event: PointerEvent) => {
|
||||
if (!filePickerRef.current?.contains(event.target as Node)) setShowFilePicker(false)
|
||||
}
|
||||
document.addEventListener('pointerdown', closeOnOutsidePointer)
|
||||
return () => document.removeEventListener('pointerdown', closeOnOutsidePointer)
|
||||
}, [showFilePicker])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) setShowFilePicker(false)
|
||||
}, [isOpen])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) return
|
||||
@@ -1182,7 +1196,7 @@ export function TransportModal({ isOpen, onClose, onSave, reservation, days, sel
|
||||
{uploadingFile ? t('reservations.uploading') : t('reservations.attachFile')}
|
||||
</button>}
|
||||
{reservation?.id && files.filter(f => !f.deleted_at && !attachedFiles.some(af => af.id === f.id)).length > 0 && (
|
||||
<div style={{ position: 'relative' }}>
|
||||
<div ref={filePickerRef} style={{ position: 'relative' }}>
|
||||
<button type="button" onClick={() => setShowFilePicker(v => !v)} className="text-content-faint" style={{
|
||||
display: 'flex', alignItems: 'center', gap: 5, padding: '6px 10px',
|
||||
border: '1px dashed var(--border-primary)', borderRadius: 8, background: 'none',
|
||||
|
||||
Reference in New Issue
Block a user