Commit Graph

992 Commits

Author SHA1 Message Date
Dries Peeters e63bfc3a75 Update local.properties 2026-02-02 15:03:39 +01:00
Dries Peeters 579dc6f7c0 feat(mobile): design system, timer/projects UX, and unified logo
Design system and theme:
- Add google_fonts and Inter TextTheme; extend AppTheme with
  navigationBarTheme, bottomSheetTheme, chipTheme, dividerTheme
- Add app_tokens.dart (AppSpacing, AppRadii, AppDurations)

Timer UX:
- Remove nested NavigationBar from TimerScreen; single focused timer view
- Replace Start Timer dialog with modal bottom sheet (project search,
  task picker, notes)
- Redesign timer card (chips, FilledButton stop, AnimatedSwitcher)
- TimerWidget: use Timer.periodic for tick; dispose correctly

Projects UX:
- Replace search TextField with Material 3 SearchBar; add RefreshIndicator
- Project rows: avatar initial, client chip, Start timer action
- Start timer from project opens sheet with project pre-selected

Logo and app icon:
- Use assets/icon/app_icon.png as in-app logo on splash and login
- Regenerate Android launcher icons from same asset; iOS config in
  flutter_launcher_icons_ios.yaml (remove_alpha_ios)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-02 14:43:19 +01:00
Dries Peeters 74c75c1b96 fix: correct invalid .gitignore glob for mobile/.android/
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>
2026-02-02 14:42:47 +01:00
Dries Peeters 689700d260 fix(timer): load tasks when selecting project on Timer and Log Time
- 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>
2026-02-02 14:11:02 +01:00
Dries Peeters 7160ce88e5 Update Podfile 2026-02-01 17:11:28 +01:00
Dries Peeters 84a4508ace Update workflow 2026-02-01 17:03:07 +01:00
Dries Peeters 930740fd08 Update Workflows 2026-02-01 16:59:47 +01:00
Dries Peeters 9b2e7cea4a Update .gitignore 2026-02-01 16:54:27 +01:00
Dries Peeters df70ac0835 Docs: update READMEs and desktop renderer
- desktop/README and docs/mobile-desktop-apps: minor updates
- desktop renderer index.html: small adjustments

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-01 16:51:21 +01:00
Dries Peeters 94fc19f6f2 Build: add icon generation to CI and scripts, bump version to 4.16.0
- Run app icon and launcher icon generation in build-mobile workflow
- Add generate-mobile-icon scripts (Python/Pillow, ImageMagick, Inkscape)
- BUILD.md: document icon requirements and troubleshooting
- setup.py: version 4.16.0

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-01 16:51:13 +01:00
Dries Peeters 9066089a34 Mobile: theme, login, sync, and launcher icons
- Align app theme with webapp (AppColors, light/dark ColorScheme); move to core/theme
- Add app config, theme mode provider, empty_state and error_view widgets
- Improve API client, sync service, and repository for time entries
- Add login screen, settings (theme toggle, logout), SSL utils for dev certs
- Android/iOS project config, Gradle wrapper, and generated launcher icons
- Add flutter_launcher_icons and icon assets (app_icon.png, README)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-01 16:51:05 +01:00
Dries Peeters 0e7656134e Backend: add API auth login and fix time entry duration calculation
- 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>
2026-02-01 16:50:42 +01:00
Dries Peeters 0c999fd780 Version bump v4.15.1 2026-01-31 08:06:13 +01:00
Dries Peeters fe53c926ed feat(calendar): user-configurable colors for calendar item types
- 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>
2026-01-31 07:48:09 +01:00
Dries Peeters 560bb0aec8 feat(kanban,tasks): multi-select filters and Kanban toolbar fixes (#464)
- 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>
2026-01-31 07:47:49 +01:00
Dries Peeters 0480b1d1c5 Version bump 4.15.0 2026-01-30 17:42:05 +01:00
Dries Peeters 34a32af665 Fix syntax error in task_service and multi_select for ORM objects
- 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.
2026-01-30 17:41:36 +01:00
Dries Peeters 8be9d82c16 feat: auto-select and gray out client when only one exists (#467)
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
2026-01-30 17:26:14 +01:00
Dries Peeters 5da5c8373c feat(kanban,gantt): quick add task from board and Gantt views (#465)
- 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
2026-01-30 17:25:56 +01:00
Dries Peeters 1f75754879 inventory: add tests, UX hint, and docs for return/waste devaluation (fixes #385)
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.
2026-01-30 16:50:20 +01:00
Dries Peeters fe0a31c583 feat(invoices): improve UX for time-based invoice items (Issue #469)
- 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
2026-01-30 16:49:54 +01:00
Dries Peeters 940e1c8170 Fix manual time entry timezone (Issue #471)
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.
2026-01-30 15:59:25 +01:00
Dries Peeters f117a0cb58 fix(calendar): show overlapping entries side-by-side and week as whole blocks
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.
2026-01-30 15:59:12 +01:00
Dries Peeters de1153f9ef Version bump: v4.14.2 2026-01-28 19:19:31 +01:00
Dries Peeters 828a78edd3 fix(dashboard): make Start Timer button open modal and submit correctly
- 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.
2026-01-28 19:17:39 +01:00
Dries Peeters d4359dd87b Task report: include incomplete tasks and fix PostgreSQL query
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.
2026-01-28 19:05:46 +01:00
Dries Peeters ee5a9b948f feat(timer): allow multiple image selection in time log notes
- 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.
2026-01-28 18:36:02 +01:00
Dries Peeters 02bdbe2411 Version bump to 4.14.1 2026-01-28 08:52:41 +01:00
Dries Peeters 73afe58c48 feat: Add multiple image upload support and mobile improvements for markdown editors
- 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
2026-01-28 08:49:50 +01:00
Dries Peeters db2cbc30e3 Fix connection error popup button text and styling
- 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
2026-01-28 08:45:11 +01:00
Dries Peeters fe928d3954 Fix time entry alignment in weekly calendar view (#458)
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.
2026-01-28 08:44:45 +01:00
Dries Peeters bf62fa33f8 fix(import): CSV/Harvest task created_by + CSRF for time-entries upload
- 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')
2026-01-28 08:44:32 +01:00
Dries Peeters d7f3683a11 fix(calendar): align time entries with timestamps in weekly view
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.
2026-01-28 07:33:58 +01:00
Dries Peeters da66384700 fix(calendar): align time entries with timestamps in day 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
2026-01-28 07:32:45 +01:00
Dries Peeters 93e21bd256 Version Bump to 4.14.0 2026-01-27 21:51:13 +01:00
Dries Peeters 5f72ae3ef5 chore: Update version to 4.14.0 across all documentation
- 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)
2026-01-27 21:50:44 +01:00
Dries Peeters ce32ce9a9c docs: Comprehensive README and documentation updates
- Fix version consistency: Update all documentation to reflect version 4.13.2
- Add Technology Stack section with complete tech overview
- Enhance Quick Start section with prerequisites and troubleshooting links
- Add System Requirements section with minimum/recommended specs
- Improve documentation organization by use case (users, admins, developers)
- Add comprehensive feature documentation links throughout README
- Enhance Features section with guide links and better categorization
- Update documentation index (docs/README.md) with missing links
- Add Contributing Quick Reference section
- Fix broken links and improve navigation
- Polish all documentation for clarity and consistency

This update makes the documentation more accessible, better organized,
and easier to navigate for all user types (new users, administrators,
developers, and troubleshooters).
2026-01-27 21:50:03 +01:00
Dries Peeters d8d1757e55 feat: Add image insertion support to time log notes
Implement image/picture insertion capability in time log notes by
replacing plain textareas with ToastUI Editor (markdown editor with
image upload support), similar to task descriptions.

Changes:
- Replace textarea with markdown editor in manual time entry form
- Replace textarea with markdown editor in edit time entry form
- Replace textarea with markdown editor in dashboard timer start form
- Update all display views to render markdown with images
- Add image upload support via existing /api/uploads/images endpoint
- Support dark mode theme switching in editors

Features:
- Drag-and-drop or toolbar button image upload
- Full markdown support (headings, bold, italic, lists, links, code, tables, images)
- Backward compatible with existing plain text notes
- Images stored in app/static/uploads/editor/ directory

This addresses GitHub discussion #455 and allows users to add visual
proof/documentation to time entries, particularly useful for
maintenance tasks and general work tracking.

Files modified:
- app/templates/timer/manual_entry.html
- templates/timer/edit_timer.html
- app/templates/main/dashboard.html
- app/templates/timer/view_timer.html
- app/templates/timer/_time_entries_list.html
- app/templates/tasks/view.html
- app/templates/projects/time_entries_overview.html
- app/templates/clients/view.html
- app/templates/client_portal/approval_detail.html
2026-01-27 21:03:07 +01:00
Dries Peeters c97318926f Version Bump 2026-01-26 14:53:56 +01:00
Dries Peeters d0cc0e08aa fix(docker): SQLite parity in start-fixed migration and verification
- Add _sqlite_path_from_url() and use it for SQLite path resolution in
  wait_for_database, cleanup, and verify_core_tables.
- run_migrations: pre/post probe and alembic/core checks work for both
  PostgreSQL and SQLite (sqlite_master, etc.).
- cleanup_corrupted_database_state: support SQLite by removing corrupted DB file
  when appropriate so migrations can recreate it; keep PostgreSQL table-drop behavior.
- verify_core_tables: for SQLite, list all tables, log path and clearer errors
  when core tables or alembic_version are missing.
2026-01-26 14:47:45 +01:00
Dries Peeters 0ece882649 fix(query-logging): SQLAlchemy 2 compat and request-scope safety
- Listen for before_cursor_execute on Engine instead of Session (SQLAlchemy 2).
- Only increment query count when has_request_context() and g.query_count exist,
  avoiding errors outside request context. Remove unused Session import.
2026-01-26 14:47:44 +01:00
Dries Peeters 2cece964b1 fix(email): load mail config inside app_context for gunicorn workers
- Wrap DB-backed email settings read in app.app_context() so it works when
  create_app() runs in gunicorn workers without a request context.
- Aligns SQLite and PostgreSQL behavior on worker startup.
2026-01-26 14:47:43 +01:00
Dries Peeters dd1441c17d fix(timer): use local date for manual entry default instead of UTC
- Compute 'today' from local getFullYear/getMonth/getDate so timezones ahead of
  UTC no longer show the wrong default date in the manual entry form.
2026-01-26 14:47:41 +01:00
Dries Peeters 75d5cff4d2 fix(dashboard): throttle real-time updates and only run on dashboard page
- Add isDashboardPage() and skip initRealTimeUpdates/updateDashboardData when not on dashboard.
- Throttle updateDashboardData to at most once per 5 seconds (MIN_UPDATE_INTERVAL_MS).
- Clear existing realTimeUpdateInterval before creating a new one to avoid stacked intervals.
2026-01-26 14:47:37 +01:00
Dries Peeters f79dbdfe1a .zip\ and copy updated for JSON vs ZIP.
- Always remove temp file in finally (including on ZIP path).
- Docs: clarify JSON (export-style) vs ZIP (full system backup, no import history).
2026-01-26 14:47:36 +01:00
Dries Peeters 259c28e266 fix(time-tracking): recalculate duration when start/end time is edited
- TimeTrackingService.update_entry() now calls calculate_duration() when
  start_time or end_time is changed so duration_seconds stays correct.
- Add test_update_entry_recalculates_duration_when_start_end_edited (fixes #451).
2026-01-26 14:47:31 +01:00
Dries Peeters 12074fc29b fix: resolve integration test failures (install config dir, settings flush)
- Make InstallationConfig config dir overridable via INSTALLATION_CONFIG_DIR
  so tests and CI use a writable path instead of /data (fixes PermissionError
  on redirect to /admin/settings after logo upload).
- Set INSTALLATION_CONFIG_DIR in conftest before app import and in
  ci-comprehensive.yml for integration-tests and full-test-suite jobs.
- In Settings.get_settings(), add _session_in_flush() and a re-entrancy
  guard to skip add+commit when called during another commit's flush,
  fixing ResourceClosedError in currency_display test setup.
- Update test_installation_config fixture to set INSTALLATION_CONFIG_DIR
  so it continues to use its temp dir with the new env-based behavior.
2026-01-25 10:09:29 +01:00
Dries Peeters afd793f1cb fix(models): persist Client custom fields and ClientNote cascade delete in tests
- Client: call flag_modified() after mutating custom_fields in
  set_custom_field() and remove_custom_field() so SQLAlchemy persists
  JSON changes (in-place dict updates are not tracked by default).
  Fixes test_count_clients_with_value_ignores_empty and
  test_count_clients_with_value_ignores_other_fields.

- ClientNote: add ondelete=CASCADE to client_id FK so schema from
  db.create_all() matches migration 024 and notes are deleted when
  client is deleted. Fixes test_client_note_cascade_delete.
2026-01-25 10:03:25 +01:00
Dries Peeters 8f3041f260 Fix Client.notes backref: add delete-orphan cascade and fix dynamic loader test
- Add cascade='all, delete-orphan' to the notes backref so client notes
  are removed when a client is deleted.
- In test_client_has_notes_relationship use client.notes.count() instead
  of len(client.notes) since the backref uses lazy='dynamic'.
2026-01-25 09:44:46 +01:00
Dries Peeters dffcf04b24 fix(tests): resolve routes unit test failures in CI
- test_client_portal_dashboard_requires_access: expect 302 redirect to
  client portal login instead of 403. The client portal 403 handler
  redirects authenticated non-portal users to login by design.
- Run routes unit group with -n 0 in CI to avoid SQLite 'database is
  locked' errors from audit logging under pytest-xdist parallel workers;
  fixes client_portal and admin client-portal test failures.
2026-01-25 09:41:39 +01:00