64991eb3c9
* feat(salary): add remove-employee button to draft salary runs
The DELETE /api/salary/runs/{id}/employees/{employeeId} endpoint already
existed (draft-only, cascades to the employee's line items) but had no UI
trigger, so a mistakenly added employee could only be cleared by deleting
the whole draft. Add a trash-icon action column to the "Anställda" table,
gated on draft status + write permission to match the endpoint's guard,
with a confirm prompt and success/error toast.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(settings): prevent horizontal overflow on mobile
The company settings invite form was a non-wrapping fixed-width flex row that overflowed narrow viewports, forcing the full-screen settings modal to scroll on the x-axis. Stack the form vertically on mobile (sm:flex-row at and above the sm breakpoint) and add the missing min-w-0 guard to the modal content pane.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(bookkeeping): move journal entry filters into a filter dialog
The ledger toolbar showed every filter inline (fiscal year, sort, series,
date range, missing-documents toggle), which felt cluttered. Keep only the
search field visible and move the rest into a "Filtrera" dialog with an
active-filter count badge.
- JournalEntryList now owns the fiscal-year scope, restored from the same
localStorage key FiscalYearSelector writes, so the page no longer renders
the selector separately.
- Filters apply live and the dialog stays open; "Rensa alla filter" clears them.
- Export STORAGE_KEY_PREFIX / ALL_YEARS_VALUE from FiscalYearSelector so the
list reuses the persisted selection without duplicating the key.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(transactions): implement imported transaction guard for deletion
- Added a guard to prevent deletion of transactions that are imported via bank sync or file uploads.
- Introduced `isImportedTransaction` utility to determine if a transaction is user-created or imported.
- Updated DELETE endpoint to return a 409 status for attempts to delete imported transactions.
- Enhanced transaction history and inbox components to reflect the new deletion rules.
- Added tests for transaction origin determination and deletion behavior.
- Updated UI components to include a confirmation dialog for clearing journal entry forms.
- Localized new strings for clearing form functionality in English and Swedish.
* feat(transactions): enhance transaction deletion guard and improve fiscal year visibility
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
63 lines
2.4 KiB
TypeScript
63 lines
2.4 KiB
TypeScript
'use client'
|
|
|
|
import { Suspense } from 'react'
|
|
import { SettingsRail } from './SettingsRail'
|
|
import { SettingsLoadingSkeleton } from './SettingsLoadingSkeleton'
|
|
import { SettingsProvider } from './useSettings'
|
|
import { SETTINGS_SECTIONS } from './sections'
|
|
|
|
interface SettingsShellProps {
|
|
variant: 'page' | 'modal'
|
|
/** Resolved section id. Required for the modal (drives which content renders);
|
|
* optional for the page where `{children}` is the route's own content. */
|
|
activeSection?: string
|
|
children?: React.ReactNode
|
|
}
|
|
|
|
/**
|
|
* Shared two-pane settings layout (category rail + content). The full-page
|
|
* route renders it via `settings/layout.tsx` (content = the route's children);
|
|
* the routed modal renders it inside a Dialog (content resolved from the
|
|
* section map). Keeping one shell means page and modal stay visually identical.
|
|
*/
|
|
export function SettingsShell({ variant, activeSection, children }: SettingsShellProps) {
|
|
if (variant === 'modal') {
|
|
const Section = activeSection ? SETTINGS_SECTIONS[activeSection] : undefined
|
|
return (
|
|
// One provider per open shell: the section it wraps is swapped on tab change
|
|
// without remounting the shell, so the settings fetch is shared across tabs.
|
|
<SettingsProvider>
|
|
<div className="flex min-h-0 flex-1">
|
|
<aside className="hidden w-56 shrink-0 overflow-y-auto border-r border-border p-3 md:block">
|
|
<SettingsRail variant="modal" display="rail" activeId={activeSection} />
|
|
</aside>
|
|
<div className="min-h-0 min-w-0 flex-1 overflow-y-auto p-6">
|
|
<div className="mb-6 md:hidden">
|
|
<SettingsRail variant="modal" display="select" activeId={activeSection} />
|
|
</div>
|
|
<Suspense fallback={<SettingsLoadingSkeleton />}>
|
|
{Section ? <Section /> : null}
|
|
</Suspense>
|
|
</div>
|
|
</div>
|
|
</SettingsProvider>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<SettingsProvider>
|
|
<div className="grid gap-8 md:grid-cols-[220px_1fr]">
|
|
<aside className="md:sticky md:top-8 md:self-start">
|
|
<div className="mb-4 md:hidden">
|
|
<SettingsRail variant="page" display="select" />
|
|
</div>
|
|
<div className="hidden md:block">
|
|
<SettingsRail variant="page" display="rail" />
|
|
</div>
|
|
</aside>
|
|
<div className="min-w-0">{children}</div>
|
|
</div>
|
|
</SettingsProvider>
|
|
)
|
|
}
|