Fixes manual time entry task loading, adds a worked-time helper, makes Time Entries filters reliable, and adds CSV export for the current filtered view.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Add issuer fallback from OIDC_ISSUER when userinfo lacks iss (fixes Authelia)
- Fallback to unverified id_token decode for iss when ID token parsing failed
- Wrap authorize_access_token() in dedicated try/except; log token_exchange_failed
and suggest session cookie/proxy checks when state or PKCE validation fails
- Log reason=... before every redirect to login in callback for easier debugging
- Add 'Redirect loop / callback returns to login' troubleshooting to OIDC_SETUP.md
Fixes#486
Co-authored-by: Cursor <cursoragent@cursor.com>
- Add settings.locked_client_id and admin UI to select locked client
- Allow disabling Clients for non-admins while keeping admin access
- Gate Clients UI routes and API endpoints when module is disabled
- Auto-select and enforce the locked client across filters and form submissions
Co-authored-by: Cursor <cursoragent@cursor.com>
Admins can hide whole app modules (Analytics, Finance & Expenses, CRM, etc.)
per role so users in that role neither see them in the nav nor access them
by URL/API.
- Add Role.hidden_module_ids (JSON denylist) and migration
- Extend ModuleRegistry.is_enabled() with role-based hide check; module is
hidden only if ALL of the user's roles hide it (super admins bypass)
- Add Module visibility section to role create/edit form with checkboxes
by category; persist via hidden_modules form field
- Add tests for registry hide/allow semantics and route decorator 403
Closes#484
Co-authored-by: Cursor <cursoragent@cursor.com>
Add a new User Report export that outputs one row per time entry with selectable columns (date/user/project/task/duration/notes by default), while keeping the existing summary/overtime export.
Refs: #483
Co-authored-by: Cursor <cursoragent@cursor.com>
Add has_enabled_modules(category) helper and wrap Finance & Expenses
folder so it only renders when at least one FINANCE module is enabled.
Fixes empty folder visible after disabling all modules in a category.
Closes#481
Co-authored-by: Cursor <cursoragent@cursor.com>
When the IdP issues a large id_token (e.g. with groups claim), storing it in Flask's cookie session can exceed ~4KB and cause the browser to drop the cookie, leading to redirect loops between /auth/oidc/callback and /login.
Store id_token in Redis/in-memory cache; keep only a small reference key (oidc_id_token_key) in the session cookie. On logout, resolve id_token from cache for RP-initiated logout; support legacy session for backwards compatibility. Add regression test for oversized id_token and update OIDC logout tests.
Fixes#486
Co-authored-by: Cursor <cursoragent@cursor.com>
Replace trailing backslash (mobile\\.android\\) with forward slash
(mobile/.android/) so ripgrep and other tools can search the repo.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Use /api/projects/<id>/tasks instead of /api/tasks?project_id= so task
loading matches the working Edit Logged Time flow.
- Add credentials: 'same-origin' and response.ok checks for reliable
session auth and error handling.
- Render JS-embedded i18n strings with |tojson to avoid breakage in
non-English locales.
Fixes#480
Co-authored-by: Cursor <cursoragent@cursor.com>
- desktop/README and docs/mobile-desktop-apps: minor updates
- desktop renderer index.html: small adjustments
Co-authored-by: Cursor <cursoragent@cursor.com>
- Add POST /api/v1/auth/login (rate-limited) returning API token for mobile
- Fix time entry duration when DB returns timezone-aware datetimes (_naive_dt)
- Add _parse_date_range helper; expose timezone in API info
- Extend time entries API tests
Co-authored-by: Cursor <cursoragent@cursor.com>
- Add User preferences: calendar_color_events, calendar_color_tasks,
calendar_color_time_entries (nullable hex)
- Calendar API: attach color to each event/task/time_entry and return
typeColors; view_calendar passes type_colors to template
- PATCH /api/preferences: accept and validate calendar color fields
- Migration 117: add the three user columns
Events, tasks, and time entries can now be distinguished by user-chosen
colors (defaults: blue, amber, green). Frontend color pickers and
legend are on the calendar page (already in previous commit).
Co-authored-by: Cursor <cursoragent@cursor.com>
- Tasks list: add form_id to Project/Assigned To multi-select so Apply triggers AJAX filter refresh
- Tasks export: support project_ids and assigned_to_ids (multi-select) using same parse_ids logic as list view
- Page header: overflow-visible and z-20 so Kanban filter dropdowns appear above the board
- Kanban toolbar: align Add task/Manage Columns with dropdowns (items-end) and consistent button styling
fix(calendar): entry click opens modal near item, time entries link to /timer/edit/ (#475)
- Open popup modal with basic details and 'Go to all details' for all entry types (time entry, event, task) in both timer and custom calendar
- Position modal near the clicked item instead of centered
- Ensure time entries (registered time) always navigate to /timer/edit/: infer type from item_type, type, and props so wrong item_type does not send users to /calendar/event/ (404)
- Make time entries clickable in custom calendar day view (remove pointer-events: none)
- Timer calendar: show correct detail URL and modal title per type; hide Delete/Duplicate for non-time-entry types
Co-authored-by: Cursor <cursoragent@cursor.com>
- task_service.py: Remove stray fragment and duplicate lines at end of
list_tasks that caused SyntaxError (unmatched ')') and gunicorn
worker boot failure.
- multi_select.html: Resolve 'User' object has no attribute 'get' on
Kanban board by supporting both dict-like and ORM items. Use Jinja
mapping test and getattr for object attributes so Assigned To filter
works with User instances.
When a single-client team has only one active client, pre-fill and disable
the client selection across manual time logging, project creation, and
similar forms to reduce friction.
- Add reusable client_select macro for single vs multi-client rendering
- Pass only_one_client and single_client to all relevant forms
- Invalidate dashboard cache on client create/archive/activate/delete
- Restore single client when project selection is cleared (timer forms)
- Add tests for single-client manual entry form
- Add '+ Add task' button in Kanban and Gantt headers linking to create
with project (and status for Kanban) pre-filled
- Support 'next' redirect after task create; validate to allowed paths
(/kanban, /gantt, /tasks, /projects) to avoid open redirects
- Honor Initial Status on create: TaskService and form now pass status
through; create form pre-fills status from query and hidden 'next'
- Per-column '+' on Kanban to add task into that column (status pre-set)
- Return user to same Kanban/Gantt view after creating a task
Stock devaluation for return and waste movements was already implemented via valuation layers (stock lots). This change hardens and documents it:
- Add route tests: return with devaluation creates devalued lot; waste with devaluation consumes from devalued lot.
- Add API tests for POST /api/v1/inventory/movements with devalue_enabled (return and waste).
- Add discoverability hint on Record Movement form when type is Return or Waste.
- Document stock devaluation in INVENTORY_MANAGEMENT_PLAN.md (subsection 5.3) and list devaluation in movement_type.
- Show Project and Task instead of Stock Item and Warehouse for items from time entries
- Make quantity read-only for time-based items (billed hours) to prevent accidental edits
- Preserve time_entry_ids when saving invoice edits (fixes lost linkage)
- Enforce quantity from time entries on save for defense in depth
- Add task_name_from_time_entries property to InvoiceItem
- Add translations for new UI strings
Manual time entries were interpreted in the application timezone instead of the user's timezone, so users in a different timezone (e.g. GMT+1) saw times adjusted to the server timezone (e.g. GMT+0) on the calendar.
- Add parse_user_local_datetime() in timezone utils to parse form date/time as the user's local time and return naive datetime in app timezone for storage.
- Use parse_user_local_datetime() in the manual entry form so submitted times are stored correctly and display at the right slot in the calendar.
Calendar events and drag-created entries were already correct; only the manual entry form path is changed. Other callers of parse_local_datetime are unchanged.
Day and week view: assign overlap columns so multiple time entries/events in the same timeframe render next to each other instead of stacking (fixes#472). Week view: render events as single blocks spanning their full duration (top/height in px) instead of per-hour segments.
- Add assignOverlapColumnsByTime/ByPosition and columnStyle() for lane layout.
- Day view: merge events + time entries, assign columns, set left/width per column.
- Week view: replace 24-row table with full-day columns (1440px); add renderWeekDayBlocks() for whole-block layout; style week-event-block like day cards.
- Parse project_id, client_id, task_id, template_id safely in timer.start_timer to avoid 500 when form sends empty strings.
- Sync Toast UI Editor notes into hidden textarea before programmatic submit so notes are included in the POST.
- Add inline onclick on Start Timer button so the modal opens regardless of script load order or DOMContentLoaded.
- Keep script-based close and form handling; add direct click binding, modal content stopPropagation, and aria-hidden for robustness.
Show all tasks that have time entries in the selected date range, regardless of task status (addresses GitHub discussion #450). Add Status column and display completed_at as '-' when missing. Apply same logic to Excel export. Fix PostgreSQL 'ORDER BY must appear in SELECT list' when using DISTINCT by resolving distinct task IDs in a subquery, then loading tasks with desired ordering.
- Enable selecting and uploading multiple images at once when adding
images to time entry notes (manual entry, edit entry, dashboard)
- Replace ToastUI getUI() usage with DOM queries to find toolbar and
image button (getUI is not available in this editor version)
- Use setAttribute('multiple') and explicit toolbar lookup for reliable
multiple-file picker on desktop and mobile
- Insert images via getMarkdown/setMarkdown so they render correctly
in WYSIWYG mode instead of insertText (which did not render images)
- Add retry logic and multiple selectors for image button interception
- Add file type validation (PNG, JPG, GIF, WebP) and user feedback
- Use regular function expressions and safety checks to avoid syntax
errors in older runtimes
Addresses GitHub Discussion #455 (Add image/picture insertion on time
log note) - client requested ability to add multiple photos at once.
- Implement multiple image upload functionality in ToastUI markdown editors
- Replace single image upload with bulk upload capability
- Intercept image button click to use custom file input with multiple attribute
- Upload multiple images via new bulk upload endpoint
- Insert all uploaded images as markdown at cursor position
- Include fallback to single image upload if interception fails
- Add mobile-responsive styles for ToastUI Editor modals
- Adjust modal sizing and positioning for mobile devices
- Make file inputs and buttons touch-friendly (min-height: 44px)
- Prevent iOS zoom on input focus (font-size: 16px)
- Improve modal accessibility on small screens
- Apply changes to dashboard, manual entry, and edit timer pages
- Normalize line endings for calendar.js and calendar.html
- Add recovery option for connection errors (status 0) to show Refresh button
- Change 'Refresh Page' to 'Refresh' for better clarity and consistency
- Improve button layout with vertical flex layout for clearer hierarchy
- Enhance Retry button styling (larger padding, medium font weight)
- Update recovery button styling to match error toast theme
- Remove redundant dark mode styles for recovery buttons
Time entries were not aligning properly with timestamp labels in the
weekly calendar view due to timezone conversion issues. Times were
being sent from the API in application timezone format without proper
conversion to the user's local timezone.
Changes:
- Added convert_time_for_calendar() helper function to convert times
from application timezone to user's local timezone
- Applied timezone conversion to time entries (start and end times)
- Applied timezone conversion to calendar events (non-all-day events only)
- FullCalendar with timeZone: 'local' now receives correctly formatted
times that align with the time slot labels
This ensures that time entries appear at the correct position matching
their actual timestamps in the weekly view.
- Set created_by=user_id when creating tasks during CSV and Harvest import
(fixes NOT NULL constraint failed: tasks.created_by, closes#459)
- Send CSRF token with time-entries CSV upload FormData so /api/import/csv
passes Flask-WTF validation (fixes 'The CSRF token is missing')
Fixes#458 - Time entries now properly align with their start and end
timestamps in both FullCalendar and custom calendar weekly views.
FullCalendar (timer calendar):
- Add explicit timeZone: 'local' to ensure consistent date interpretation
- Normalize time entry ISO format to YYYY-MM-DDTHH:mm:ss (no microseconds)
Custom calendar week view:
- Fix data loading: merge events, tasks, and time_entries from separate
API arrays into unified structure with extendedProps.item_type
- Implement proper spanning: entries now span across multiple hour cells
based on their actual start/end times
- Add precise positioning: entries align with minute-level accuracy using
percentage-based top/height calculations
- Apply same spanning logic to calendar events with duration
- Add hour labels column (00:00-23:00) for better visual reference
Time entries and events now correctly display their full duration and
align with both start and end timestamps in the weekly grid view.
Fix issue where time entries were stacking at the top of the day view
instead of being positioned according to their start and end times.
Changes:
- Convert day-events-container from flexbox to absolute positioning
- Set container height to 1440px (24 hours × 60px/hour)
- Calculate absolute top position based on entry start time
- Calculate entry height based on duration (end - start)
- Apply positioning to both time entries and calendar events
- Handle edge cases: active timers, entries spanning midnight, overlapping entries
Fixes#457
- Update README.md to reflect version 4.14.0
- Update docs/FEATURES_COMPLETE.md to version 4.14.0
- Add CHANGELOG entry for version 4.14.0 with comprehensive changes
- Align all version references with setup.py (4.14.0)