diff --git a/frontend/src/components/calendar/PeriodPopover.test.tsx b/frontend/src/components/calendar/PeriodPopover.test.tsx index 2f8cbeb..9988ceb 100644 --- a/frontend/src/components/calendar/PeriodPopover.test.tsx +++ b/frontend/src/components/calendar/PeriodPopover.test.tsx @@ -46,19 +46,28 @@ describe('PeriodPopover', () => { expect(screen.getByTestId('period-delete-button')).toHaveTextContent('Ta bort dessa dagar') }) - it('calls onDelete after confirmation', async () => { - vi.spyOn(window, 'confirm').mockReturnValue(true) - mockOnDelete.mockResolvedValue(undefined) - render() - fireEvent.click(screen.getByTestId('period-delete-button')) - await waitFor(() => expect(mockOnDelete).toHaveBeenCalled()) - }) - - it('does not call onDelete when confirmation is cancelled', () => { - vi.spyOn(window, 'confirm').mockReturnValue(false) + it('shows an inline confirmation instead of deleting immediately', () => { render() fireEvent.click(screen.getByTestId('period-delete-button')) expect(mockOnDelete).not.toHaveBeenCalled() + expect(screen.getByTestId('period-delete-confirm')).toBeInTheDocument() + }) + + it('calls onDelete after confirming inline', async () => { + mockOnDelete.mockResolvedValue(undefined) + render() + fireEvent.click(screen.getByTestId('period-delete-button')) + fireEvent.click(screen.getByTestId('period-delete-confirm-button')) + await waitFor(() => expect(mockOnDelete).toHaveBeenCalled()) + }) + + it('does not call onDelete when the inline confirmation is cancelled', () => { + render() + fireEvent.click(screen.getByTestId('period-delete-button')) + fireEvent.click(screen.getByTestId('period-delete-cancel-button')) + expect(mockOnDelete).not.toHaveBeenCalled() + expect(screen.queryByTestId('period-delete-confirm')).not.toBeInTheDocument() + expect(screen.getByTestId('period-delete-button')).toBeInTheDocument() }) }) @@ -217,20 +226,20 @@ describe('PeriodPopover', () => { }) it('displays a generic local error when onDelete rejects with a non-Error value', async () => { - vi.spyOn(window, 'confirm').mockReturnValue(true) mockOnDelete.mockRejectedValue('boom') render() fireEvent.click(screen.getByTestId('period-delete-button')) + fireEvent.click(screen.getByTestId('period-delete-confirm-button')) await waitFor(() => { expect(screen.getByRole('alert')).toHaveTextContent('Det gick inte att skapa perioden') }) }) it('calls onDelete for a wish-linked band after confirmation', async () => { - vi.spyOn(window, 'confirm').mockReturnValue(true) mockOnDelete.mockResolvedValue(undefined) render() fireEvent.click(screen.getByTestId('period-delete-button')) + fireEvent.click(screen.getByTestId('period-delete-confirm-button')) await waitFor(() => expect(mockOnDelete).toHaveBeenCalled()) }) diff --git a/frontend/src/components/calendar/PeriodPopover.tsx b/frontend/src/components/calendar/PeriodPopover.tsx index f7dd306..20a5e0b 100644 --- a/frontend/src/components/calendar/PeriodPopover.tsx +++ b/frontend/src/components/calendar/PeriodPopover.tsx @@ -101,6 +101,7 @@ export function PeriodPopover({ lastYear: null, }) const [localError, setLocalError] = useState(null) + const [isConfirmingDelete, setIsConfirmingDelete] = useState(false) useEffect(() => { if (wish) { @@ -117,6 +118,7 @@ export function PeriodPopover({ }) } setLocalError(null) + setIsConfirmingDelete(false) }, [isOpen, wish, startDate, endDate, t]) if (!isOpen) return null @@ -141,14 +143,22 @@ export function PeriodPopover({ } } - const handleDelete = async () => { - if (confirm(t('calendar.periodDeleteConfirm'))) { - try { - setLocalError(null) - await onDelete() - } catch (err) { - setLocalError(err instanceof Error ? err.message : t('calendar.periodCreateError')) - } + const handleDeleteClick = () => { + setIsConfirmingDelete(true) + } + + const handleCancelDelete = () => { + setIsConfirmingDelete(false) + } + + const handleConfirmDelete = async () => { + try { + setLocalError(null) + await onDelete() + } catch (err) { + setLocalError(err instanceof Error ? err.message : t('calendar.periodCreateError')) + } finally { + setIsConfirmingDelete(false) } } @@ -339,35 +349,61 @@ export function PeriodPopover({
- - - {wish && ( - + {isConfirmingDelete ? ( +
+ + {t('calendar.periodDeleteConfirm')} + + + +
+ ) : ( + <> + + + {wish && ( + + )} + )}
diff --git a/frontend/src/components/calendar/period-popover.css b/frontend/src/components/calendar/period-popover.css index 0fced05..f8f895b 100644 --- a/frontend/src/components/calendar/period-popover.css +++ b/frontend/src/components/calendar/period-popover.css @@ -175,3 +175,16 @@ .period-popover__button--danger:hover:not(:disabled) { background-color: #b71c1c; } + +.period-popover__delete-confirm { + display: flex; + align-items: center; + gap: 12px; + width: 100%; +} + +.period-popover__delete-confirm-text { + font-size: 14px; + color: #333; + margin-right: auto; +} diff --git a/frontend/src/components/modals/PlannedDayModal.test.tsx b/frontend/src/components/modals/PlannedDayModal.test.tsx index ef80570..a73f18b 100644 --- a/frontend/src/components/modals/PlannedDayModal.test.tsx +++ b/frontend/src/components/modals/PlannedDayModal.test.tsx @@ -429,9 +429,6 @@ describe('PlannedDayModal', () => { }) mockOnDelete.mockRejectedValue(new Error('Delete failed')) - // Mock the confirm dialog - vi.spyOn(window, 'confirm').mockReturnValue(true) - render( { const deleteButton = screen.getByTestId('delete-button') await userEvent.click(deleteButton) + await userEvent.click(screen.getByTestId('delete-confirm-button')) await waitFor(() => { expect(screen.getByRole('alert')).toHaveTextContent('Delete failed') @@ -455,7 +453,6 @@ describe('PlannedDayModal', () => { parentPersonalDataUuid: 'p1', }) mockOnDelete.mockRejectedValue('Some error') - vi.spyOn(window, 'confirm').mockReturnValue(true) render( { const deleteButton = screen.getByTestId('delete-button') await userEvent.click(deleteButton) + await userEvent.click(screen.getByTestId('delete-confirm-button')) await waitFor(() => { expect(screen.getByRole('alert')).toHaveTextContent('Det gick inte att ta bort den planerade dagen') @@ -711,38 +709,12 @@ describe('PlannedDayModal', () => { expect(screen.getByTestId('delete-button')).toBeInTheDocument() }) - it('shows confirmation dialog before deleting', async () => { + it('shows an inline confirmation instead of deleting immediately', async () => { const existingDay = makePlannedLeaveDay({ id: 'day-1', parentPersonalDataUuid: 'p1', }) - const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(true) - mockOnDelete.mockResolvedValue(undefined) - - render( - - ) - - const deleteButton = screen.getByTestId('delete-button') - await userEvent.click(deleteButton) - - expect(confirmSpy).toHaveBeenCalledWith('Är du säker på att du vill ta bort den här planerade dagen?') - confirmSpy.mockRestore() - }) - - it('does not call onDelete if confirmation is cancelled', async () => { - const existingDay = makePlannedLeaveDay({ - id: 'day-1', - parentPersonalDataUuid: 'p1', - }) - - const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(false) mockOnDelete.mockResolvedValue(undefined) render( @@ -758,16 +730,17 @@ describe('PlannedDayModal', () => { await userEvent.click(deleteButton) expect(mockOnDelete).not.toHaveBeenCalled() - confirmSpy.mockRestore() + expect(screen.getByTestId('delete-confirm')).toHaveTextContent( + 'Är du säker på att du vill ta bort den här planerade dagen?', + ) }) - it('calls onDelete if confirmation is accepted', async () => { + it('does not call onDelete if the inline confirmation is cancelled', async () => { const existingDay = makePlannedLeaveDay({ id: 'day-1', parentPersonalDataUuid: 'p1', }) - const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(true) mockOnDelete.mockResolvedValue(undefined) render( @@ -781,11 +754,37 @@ describe('PlannedDayModal', () => { const deleteButton = screen.getByTestId('delete-button') await userEvent.click(deleteButton) + await userEvent.click(screen.getByTestId('delete-cancel-button')) + + expect(mockOnDelete).not.toHaveBeenCalled() + expect(screen.queryByTestId('delete-confirm')).not.toBeInTheDocument() + expect(screen.getByTestId('delete-button')).toBeInTheDocument() + }) + + it('calls onDelete if the inline confirmation is accepted', async () => { + const existingDay = makePlannedLeaveDay({ + id: 'day-1', + parentPersonalDataUuid: 'p1', + }) + + mockOnDelete.mockResolvedValue(undefined) + + render( + + ) + + const deleteButton = screen.getByTestId('delete-button') + await userEvent.click(deleteButton) + await userEvent.click(screen.getByTestId('delete-confirm-button')) await waitFor(() => { expect(mockOnDelete).toHaveBeenCalled() }) - confirmSpy.mockRestore() }) it('disables delete button when saving', () => { diff --git a/frontend/src/components/modals/PlannedDayModal.tsx b/frontend/src/components/modals/PlannedDayModal.tsx index 0b7abd8..c2a24d6 100644 --- a/frontend/src/components/modals/PlannedDayModal.tsx +++ b/frontend/src/components/modals/PlannedDayModal.tsx @@ -55,6 +55,7 @@ export function PlannedDayModal({ const [isDubbeldag, setIsDubbeldag] = useState(false) const [localError, setLocalError] = useState(null) const [isTogglingOverride, setIsTogglingOverride] = useState(false) + const [isConfirmingDelete, setIsConfirmingDelete] = useState(false) useEffect(() => { if (existingDay) { @@ -67,6 +68,7 @@ export function PlannedDayModal({ setIsDubbeldag(false) } setLocalError(null) + setIsConfirmingDelete(false) }, [isOpen, existingDay, selectedParentId, defaultDayType]) useEffect(() => { @@ -88,15 +90,24 @@ export function PlannedDayModal({ } } - const handleDelete = async () => { + const handleDeleteClick = () => { if (!onDelete) return - if (confirm(t('plannedDayModal.confirmDelete'))) { - try { - setLocalError(null) - await onDelete() - } catch (err) { - setLocalError(err instanceof Error ? err.message : t('plannedDayModal.deleteError')) - } + setIsConfirmingDelete(true) + } + + const handleCancelDelete = () => { + setIsConfirmingDelete(false) + } + + const handleConfirmDelete = async () => { + if (!onDelete) return + try { + setLocalError(null) + await onDelete() + } catch (err) { + setLocalError(err instanceof Error ? err.message : t('plannedDayModal.deleteError')) + } finally { + setIsConfirmingDelete(false) } } @@ -256,32 +267,59 @@ export function PlannedDayModal({
- {!isRangeMode && existingDay && onDelete && ( - + {!isRangeMode && existingDay && onDelete && isConfirmingDelete && ( +
+ + {t('plannedDayModal.confirmDelete')} + + + +
+ )} + {!isConfirmingDelete && ( + <> + {!isRangeMode && existingDay && onDelete && ( + + )} + + + )} - -
diff --git a/frontend/src/components/modals/planned-day-modal.css b/frontend/src/components/modals/planned-day-modal.css index 3be985f..443fb8c 100644 --- a/frontend/src/components/modals/planned-day-modal.css +++ b/frontend/src/components/modals/planned-day-modal.css @@ -187,3 +187,16 @@ .planned-day-modal__button--danger:hover:not(:disabled) { background-color: #b71c1c; } + +.planned-day-modal__delete-confirm { + display: flex; + align-items: center; + gap: 12px; + width: 100%; +} + +.planned-day-modal__delete-confirm-text { + font-size: 14px; + color: #333; + margin-right: auto; +} diff --git a/frontend/src/i18n/en/calendar.ts b/frontend/src/i18n/en/calendar.ts index 38195bb..46d2485 100644 --- a/frontend/src/i18n/en/calendar.ts +++ b/frontend/src/i18n/en/calendar.ts @@ -40,6 +40,7 @@ export const calendar = { 'plannedDayModal.closeAria': 'Close', 'plannedDayModal.confirmDelete': 'Are you sure you want to delete this planned day?', 'plannedDayModal.deleteButton': 'Delete', + 'plannedDayModal.deleteConfirmButton': 'Yes, delete', 'plannedDayModal.deleteError': 'Failed to delete planned day', 'plannedDayModal.deletingButton': 'Deleting…', 'plannedDayModal.doubleDayHint': 'Double days do not apply to vacation', @@ -108,6 +109,7 @@ export const calendar = { 'calendar.periodDeleteButton': 'Delete period', 'calendar.periodDeletingButton': 'Deleting…', 'calendar.periodDeleteConfirm': 'Are you sure you want to delete this period?', + 'calendar.periodDeleteConfirmButton': 'Yes, delete', 'calendar.periodDeleteAdHocButton': 'Delete these days', 'calendar.periodCancelButton': 'Cancel', 'calendar.periodCloseAria': 'Close', diff --git a/frontend/src/i18n/sv/calendar.ts b/frontend/src/i18n/sv/calendar.ts index ba2e208..f0e4f4d 100644 --- a/frontend/src/i18n/sv/calendar.ts +++ b/frontend/src/i18n/sv/calendar.ts @@ -40,6 +40,7 @@ export const calendar = { 'plannedDayModal.closeAria': 'Stäng', 'plannedDayModal.confirmDelete': 'Är du säker på att du vill ta bort den här planerade dagen?', 'plannedDayModal.deleteButton': 'Ta bort', + 'plannedDayModal.deleteConfirmButton': 'Ja, ta bort', 'plannedDayModal.deleteError': 'Det gick inte att ta bort den planerade dagen', 'plannedDayModal.deletingButton': 'Tar bort…', 'plannedDayModal.doubleDayHint': 'Dubbeldagar gäller inte för semester', @@ -108,6 +109,7 @@ export const calendar = { 'calendar.periodDeleteButton': 'Ta bort period', 'calendar.periodDeletingButton': 'Tar bort…', 'calendar.periodDeleteConfirm': 'Är du säker på att du vill ta bort den här perioden?', + 'calendar.periodDeleteConfirmButton': 'Ja, ta bort', 'calendar.periodDeleteAdHocButton': 'Ta bort dessa dagar', 'calendar.periodCancelButton': 'Avbryt', 'calendar.periodCloseAria': 'Stäng',