Was showing only worked hours, but +/- was calculated against covered.
Now both columns are consistent — Odpracováno includes vacation, sick,
and holiday hours so the numbers make sense with the +/- column.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Card header, per-user +/-, progress bar, and yearly table all now
use the same prorated fund_to_date for the current month.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend: per-user missing/overtime in current month now calculated
against bizDaysToDate (working days up to today), not full month.
Frontend: monthly card percentage and fulfilled check also use
fund_to_date for current month.
Now both the yearly summary table and the monthly cards agree.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Monthly cards show full month fund (e.g., 168h for 21 days).
Yearly summary table uses fund_to_date (prorated to today for
current month) so the +/- column is accurate mid-month.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
API returns scope_template_sections (Prisma relation name) but
frontend was reading sections. Now checks both field names.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
RichEditor now supports readOnly prop — hides toolbar and disables
editing via ReactQuill's built-in readOnly. Content renders with
proper Quill CSS (list margins, indentation, fonts) instead of
broken browser defaults from dangerouslySetInnerHTML.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
admin-form-input has fixed height (36px) causing overflow. Replaced
with a styled div matching the editor appearance. No new CSS needed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Added section-content class with proper ul/ol/li/p margins and
overflow:hidden. Browser defaults for lists were causing content
to extend outside the form input box.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Added isLockedByOther check to:
- All disabled={} on selects (currency, language) and checkboxes (apply_vat)
- All conditional renders that swap date pickers for read-only inputs
- Rich editor conditional that swaps editor for static HTML display
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When user A opens an offer, a lock is acquired (locked_by + locked_at).
User B opening the same offer sees a warning banner and the form is
read-only. Lock expires after 5 minutes without heartbeat.
Backend:
- POST /:id/lock — acquire lock (returns 423 if locked by another)
- POST /:id/heartbeat — refresh lock timestamp (every 2 min)
- POST /:id/unlock — release lock
- GET /:id — includes locked_by info
- PUT /:id — auto-releases lock on save
Frontend:
- Acquires lock on page load (edit mode only)
- Sends heartbeat every 2 minutes
- Releases lock on page unmount (navigate away)
- Shows warning banner with locker's name
- All inputs read-only + action buttons hidden when locked
Migration: adds locked_by (INT) and locked_at (DATETIME) to quotations.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The TOTP verification endpoint always created refresh tokens with
remember_me=false and 1-hour expiry, regardless of what the user
selected at login.
Fix:
- Frontend now sends remember_me in the TOTP verify request body
- Backend reads remember_me and uses it for token expiry (30 days)
and cookie maxAge
Users with 2FA who checked "remember me" will now stay logged in
for 30 days instead of being kicked out after 1 hour.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Added billing_text column to invoices table (VARCHAR 500)
- Prisma migration: 20260323_add_billing_text
- Form field on invoice create page with placeholder
- PDF uses billing_text, falls back to default translation
- Stored on create and editable on draft invoices
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Changed from offers-editor-section + offers-items-table to
admin-card + admin-card-body + admin-table-responsive, matching
the offer detail page structure.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. Item reordering: replaced placeholder with @dnd-kit drag-and-drop.
Each item row has a drag handle for reordering via vertical drag.
Uses SortableContext with verticalListSortingStrategy.
2. Scope template insertion: fixed template loading to use already-fetched
data instead of re-fetching from non-existent endpoint. Templates with
sections are now stored fully and inserted directly on selection.
Also copies template description to scope_description.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
API now only returns data sections the user has permission to see:
- my_shift: attendance.record
- attendance: attendance.admin
- offers: offers.view
- projects: projects.view
- invoices: invoices.view
- orders: orders.view
- leave_pending: attendance.approve
- recent_activity: settings.audit
Frontend hides KPI cards, activity feed, and attendance sections
for users without the matching permissions.
Regular employees now only see their shift status, quick actions,
profile, and sessions — not company KPIs or admin data.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Added white-space: nowrap to .admin-btn (prevents text wrapping)
- Modal footer buttons get min-width: 100px for consistent sizing
- Spinner in buttons doesn't add extra vertical space
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Component used admin-pagination-btn/pages/dots but CSS has
admin-pagination-page/controls/ellipsis. Fixed to match existing CSS.
Added record count display.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Times in the database are stored as local time (CET). JavaScript's
Date constructor treated them as UTC, then toLocaleTimeString added
+1 hour for CET timezone.
Fix: extract hours/minutes directly from the datetime string via regex
instead of going through Date object. No timezone conversion applied.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Added GET /api/admin/received-invoices/suppliers endpoint (distinct names)
- Upload and edit forms use HTML datalist for browser-native autocomplete
- Suggestions loaded once on page mount
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Connects the existing UI button to GET/POST /api/admin/totp/required
endpoints. Fetches current state on load, toggles on click.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two separate useState calls (sort + order) caused React to skip
re-renders when clicking the same column — setSort returned the same
value so React bailed out, and the nested setOrder was lost.
Single state object guarantees a new reference on every click,
so React always re-renders and useListData always refetches.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: useListData set loading=true on every refetch, and all 4
admin list pages (offers, orders, invoices, projects) applied
pointerEvents:'none' while loading — blocking all clicks including
sort column headers.
Fix: removed setLoading(true) from refetch (matching PHP behavior)
and removed pointerEvents from all list page cards. Opacity fade
kept as visual feedback.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the file management placeholder with the actual ProjectFileManager
component, providing projectId, projectNumber, hasPermission, and hasNasFolder
props from the existing page state.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Dark table headers (#333), proper column widths, uppercase labels
- User header bar with gray background and total hours
- Records from userData.records (not filtered from global records)
- Fund row with covered/total and status badge
- Leave summary with vacation remaining
- Print wrapper table for repeating header
- Matching CSS: borders, fonts, spacing, badges
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>