Commit Graph

405 Commits

Author SHA1 Message Date
lukas c7c196a054 Fix non-filter select visuals 2026-06-09 11:56:01 +02:00
lukas c639196266 Search select JavaScript improvements 2026-06-09 11:48:36 +02:00
lukas ed086c9702 Fix prefetch 2026-06-09 11:37:41 +02:00
lukas 5c9bf45c61 Fix contrast issues with search select dropdown 2026-06-09 09:41:30 +02:00
lukas 1c9fb474df Unify UI for filter modifiers
Django CI/CD / test (push) Successful in 40s
Django CI/CD / build-and-push (push) Successful in 1m16s
2026-06-09 08:47:20 +02:00
lukas 737dd9275b Fix includes all query returning duplicates 2026-06-09 08:47:20 +02:00
lukas 9f436b245d Decrease debounce for search select 2026-06-09 08:47:20 +02:00
lukas a7ff2962a6 Add number of games filter to purchases 2026-06-09 08:47:20 +02:00
lukas 103219a5e7 Add includes only matcher mode 2026-06-09 08:47:20 +02:00
lukas 14efff8078 Fix filter stuff 2026-06-09 08:47:20 +02:00
Claude ba9b92d419 Align set-criterion modifiers with Stash (any/all/none) and harmonize EXCLUDES
Closes #10.

Backend (common/criteria.py):
- Treat `excludes` as an always-orthogonal AND'd negative across both
  MultiCriterion and ChoiceCriterion; the modifier now governs only the
  `value` (include) set. This removes the prior divergence where
  MultiCriterion.EXCLUDES dropped the excludes list and ChoiceCriterion.EXCLUDES
  swapped include/exclude into a positive.
- Fold INCLUDES / INCLUDES_ALL / EXCLUDES (+ EQUALS/NOT_EQUALS aliases) into the
  shared _SetCriterion base so the two subclasses cannot drift; remove _extra_q.

M2M "has all" (games/filters.py):
- PurchaseFilter._games_to_q builds a pk__in subquery with one join per value so
  INCLUDES_ALL on the many-to-many games field works in a single .filter()
  (a naive Q(games=a) & Q(games=b) collapses to one join and matches nothing).

UI (FilterSelect + filter_bar.js):
- Add an optional any/all/none match-mode <select> (INCLUDES/INCLUDES_ALL/
  EXCLUDES) rendered before the pills via a new `leading` slot on the shared
  combobox shell. A native control so its value is its state. readSearchSelect
  serialises it to data-match; filter_bar folds it into the criterion modifier.
  Orthogonal to the (Any)/(None) presence pseudo-options and the exclude channel.
- Enable it for the M2M Purchase.games field (INCLUDES_ALL is only meaningful
  for multi-valued relations). Styled with already-compiled utilities.

Tests: harmonized EXCLUDES + INCLUDES_ALL for both criterion types, a DB-backed
INCLUDES_ALL vs INCLUDES contrast on Purchase.games, and FilterSelect /
PurchaseFilterBar rendering + round-trip of the match mode.

https://claude.ai/code/session_01KwVrGFbq13mZdhDL9G6zhg
2026-06-09 08:47:20 +02:00
Claude d9902146dc Clean up label-embedding architecture
- Move {id,label} stripping into _SetCriterion.from_json() so both
  MultiCriterion and ChoiceCriterion normalise at the parse boundary;
  the querying layer stays typed (list[int] / list[str]) and clean.
- Revert MultiCriterion to a thin _extra_q() override; _SetCriterion.to_q()
  is no longer duplicated.
- JS: readSearchSelect always emits {id, label} objects — no conditional
  mixed-type arrays. filter_bar.js stores them as-is for all fields,
  removing the fragile isIdField hardcoded list.
- Update tests to use the {id, label} filter format.

https://claude.ai/code/session_01EyAJcMoDktLrY9tSbdHViA
2026-06-08 21:41:03 +02:00
Claude 0285243172 Embed labels in filter criteria (Stash-style) to retire pill resolver
Store {id, label} objects instead of bare IDs in MultiCriterion value/excludes.
FilterSelect pills now render directly from the embedded labels — no DB round-trip
to _resolve_game/device/platform_options. The filter URL and saved presets are
self-describing. MultiCriterion.to_q() extracts ids for querying; bare ints are
still accepted for backward compatibility.

Closes #9

https://claude.ai/code/session_01EyAJcMoDktLrY9tSbdHViA
2026-06-08 21:41:03 +02:00
lukas 60773e7755 Fix variable names 2026-06-08 19:12:28 +02:00
Claude 15bb3ce1b9 Expand the ss namespace prefix to search-select everywhere
Spell out the abbreviated data-ss-* hook attributes (data-search-select-option,
-label, -mode, -template, -action, -type, -modifier, -modifier-option, -pills,
-search, -options, -no-results) and the JS expando properties (_searchSelectInit,
_searchSelectLabel, _searchSelectDirty, _searchSelectOption) across components,
JS, and tests — no abbreviations left in the widget's hooks.

https://claude.ai/code/session_01XzhXvMvw42CQGc9kmin3GS
2026-06-08 19:12:28 +02:00
Claude a06e772e42 Rename data-ss-tpl attribute to data-ss-template
Spell out the abbreviation in the template marker attribute too, matching the
complete-words convention applied to the variables.

https://claude.ai/code/session_01XzhXvMvw42CQGc9kmin3GS
2026-06-08 19:12:28 +02:00
Claude 29b42e0f3d Use complete words for variable names; document the convention
Rename abbreviated identifiers in the PR's code to full words: tpl→template,
e→event, el→element, removeBtn→removeButton, and single-letter loop variables
(o→option, g/d/p→game/device/platform, v→value/modifier_value). Add a
'name variables with complete words' convention to CLAUDE.md.

https://claude.ai/code/session_01XzhXvMvw42CQGc9kmin3GS
2026-06-08 19:12:28 +02:00
Claude f210f818a9 Single-source combobox markup via <template> cloning
Eliminate the Python/JS class-string duplication: the server renders hidden
<template> prototypes (row, pill, include/exclude/modifier pills) using the same
component functions, and search_select.js clones them, filling only the
[data-ss-label] slot, value, and data-* attrs. All Tailwind class strings and DOM
structure now live solely in the Python components — the JS no longer hardcodes
any class. Pill gains an opt-in label_slot; the shell takes a templates list.

Companion issue #8 tracks the further HTMX-idiomatic step of returning rendered
row HTML from the search endpoint.

https://claude.ai/code/session_01XzhXvMvw42CQGc9kmin3GS
2026-06-08 19:12:28 +02:00
lukas 6bc7da9f2f Fix button visibility on select option hover 2026-06-08 19:12:28 +02:00
Claude 6aff12b7b2 Address PR review: combobox field layout and dark-mode contrast
- Wire the long-defined-but-unused _FIELD_CLASS into the container so pills and
  the search input form a single padded flex row; the flex-1 input now fills the
  widget instead of looking unclickable inside a larger box (affects both
  SearchSelect and FilterSelect via the shared shell).
- Filter option labels get text-body so they're readable on dark backgrounds.
- Filter +/- buttons get text-body (readable at rest) and hover:border-brand-strong
  so the border stays visible against the brand hover fill.
- Mirror the filter class changes in search_select.js and rebuild base.css.

https://claude.ai/code/session_01XzhXvMvw42CQGc9kmin3GS
2026-06-08 19:12:28 +02:00
Claude 12b0b0af61 Remove the bespoke SelectableFilter widget
FilterSelect fully replaces it: delete SelectableFilter and its _selectable_*
helpers, the now-unused _get_filter_options, selectable_filter.js, and the .sf-*
rules in input.css (rebuilt base.css). The three list views load search_select.js
instead of selectable_filter.js. Drop the SelectableFilter export and refresh
docs/comments that referenced it.

https://claude.ai/code/session_01XzhXvMvw42CQGc9kmin3GS
2026-06-08 19:12:28 +02:00
Claude 1a206d719b Migrate filter bars to FilterSelect
Replace the bespoke SelectableFilter in all three bars with FilterSelect: enum
fields (status, type, ownership) pre-render their fixed options; model-backed
fields (game(s), platform, device) use the search endpoints with prefetch and
resolve only the selected ids to pill labels — dropping the per-page queries that
fetched every game/platform/device. filter_bar.js now reads filter-mode
SearchSelect widgets via readSearchSelect (data-included/excluded/modifier),
preserving the {value, excludes, modifier} JSON and id Number() coercion; the
redundant session game/device blocks are gone. Drop FilterBar's now-unused
platform_options param. Rebuild base.css for the inline filter-pill utilities and
update the bar tests to the new markup.

https://claude.ai/code/session_01XzhXvMvw42CQGc9kmin3GS
2026-06-08 19:12:28 +02:00
Claude a6532807cb Wire filter-mode behavior into search_select.js
Dispatch on data-ss-mode: in filter mode, value rows (server-rendered or fetched
via buildRow) carry +/- buttons that add include/exclude pills, and pinned
modifier pseudo-options set a lone, mutually-exclusive modifier pill. Pill removal
handles the modifier flag; filter pills carry no hidden inputs. Extend
readSearchSelect to serialise filter widgets into data-included / data-excluded /
data-modifier (the shape the filter bar consumes), leaving form widgets'
data-values path unchanged. JS class strings mirror the FilterSelect constants.

https://claude.ai/code/session_01XzhXvMvw42CQGc9kmin3GS
2026-06-08 19:12:28 +02:00
Claude 003e6ebe15 Add prefetch + instant-local/debounced-remote search to combobox
Introduce a general 'prefetch' option (rows to load on first open, default 0 =
unchanged) carried as data-prefetch. Rework the JS search so a search_url widget
filters its loaded window instantly on every keystroke while issuing a debounced
server request for the rest, with an AbortController so a slower earlier response
can never overwrite a newer one. No-results stays hidden until the server
response decides it, avoiding a flash over an incomplete window. On first focus a
prefetch-enabled widget seeds its window immediately. Rename single-letter locals
to full words while reworking these functions.

https://claude.ai/code/session_01XzhXvMvw42CQGc9kmin3GS
2026-06-08 19:12:28 +02:00
Claude 061b5e6d8a Fix add_session prefilling game from last session
When no game_id is provided, the session form should start with no game
selected rather than defaulting to the last session's game.

https://claude.ai/code/session_01BZHdra2YBPwS3umwsGrgUj
2026-06-07 20:52:56 +02:00
lukas 0aa87a17fe Merge branch 'main' of github.com:KucharczykL/timetracker
Django CI/CD / test (push) Failing after 13m50s
Django CI/CD / build-and-push (push) Has been skipped
2026-06-07 20:22:50 +02:00
Claude 7c2c08501e Adopt SearchSelect for device, platform, and play event game fields
- Parameterize SearchSelectWidget with a required options_resolver so
  each widget explicitly names its resolver instead of implicitly using
  _game_options
- Add autofocus support: SearchSelect forwards it to the search input,
  and SearchSelectWidget extracts it from Django's attrs dict
- Add _device_options and _platform_options resolvers (single pk__in
  queries, same pattern as _game_options)
- Add /api/devices/search and /api/platforms/search endpoints
- Switch PlayEventForm.game from plain Select to SearchSelectWidget
  (preserving autofocus), and use SingleGameChoiceField for correct labels
- Switch SessionForm.device to SearchSelectWidget
- Switch PurchaseForm.platform and GameForm.platform to SearchSelectWidget
- Wire ModuleScript("search_select.js") into add/edit playevent and
  add/edit game views

https://claude.ai/code/session_013fpJD54HxRgxRv2xzwXGNo
2026-06-07 20:20:43 +02:00
lukas 3b9c05d674 Improve year picker on stats page
Django CI/CD / test (push) Successful in 45s
Django CI/CD / build-and-push (push) Successful in 1m16s
2026-06-07 10:48:32 +02:00
lukas a6384fc003 Improve search select 2026-06-07 09:01:18 +02:00
lukas afc16aabbb Implement search select component
Django CI/CD / test (push) Successful in 40s
Django CI/CD / build-and-push (push) Successful in 1m24s
2026-06-06 22:52:26 +02:00
lukas 3ce3356064 Refine filters 2026-06-06 19:37:14 +02:00
lukas ed8589a972 Fix more code smells
Django CI/CD / test (push) Successful in 39s
Django CI/CD / build-and-push (push) Successful in 1m19s
2026-06-06 13:14:55 +02:00
lukas f4161bf3f4 Improve stats code smells 2026-06-06 12:19:15 +02:00
lukas b6864e59ce Add filters
Django CI/CD / test (push) Successful in 43s
Django CI/CD / build-and-push (push) Successful in 1m22s
2026-06-06 12:13:04 +02:00
lukas 36b1382015 Fix code smells 2026-06-06 08:15:19 +02:00
lukas d101aecd70 Move from HTML templates to pure Python
Remove cruft
2026-06-06 07:51:10 +02:00
lukas f090643026 Keep moving towards pure Python components 2026-06-02 22:35:11 +02:00
lukas ec1828b823 Migrate cotton to Python + template tag shims
Django CI/CD / test (push) Successful in 32s
Django CI/CD / build-and-push (push) Successful in 1m22s
2026-06-02 22:19:55 +02:00
lukas 94c3d9050a Fix make init 2026-06-02 16:32:26 +02:00
lukas 5b2b79f553 Fix comment not being a comment 2026-05-12 18:56:58 +02:00
lukas 103c29e234 Fix missing values for first and last game in stats view
Django CI/CD / test (push) Successful in 22s
Django CI/CD / build-and-push (push) Successful in 53s
2026-05-12 15:12:43 +02:00
lukas 5003b739d3 PR review
Django CI/CD / test (push) Successful in 28s
Django CI/CD / build-and-push (push) Successful in 55s
2026-05-12 14:56:59 +02:00
lukas e3b53cd4a9 Add needs_price_update field to Purchase model
Django CI/CD / test (push) Successful in 22s
Django CI/CD / build-and-push (push) Has been skipped
Replace fragile price change detection in Purchase.save() with a
lazy dirty flag approach. A pre_save/post_save signal pair detects
price/currency changes without extra DB queries, and convert_prices()
uses the flag to determine which purchases need conversion.

- Add needs_price_update BooleanField with db_index
- Add pre_save signal to store old price/currency values
- Add post_save signal to set needs_price_update=True when price/currency changes
- Simplify Purchase.save() to remove DB reload + comparison logic
- Remove price_or_currency_differ_from() method
- Update convert_prices() to filter on needs_price_update flag
- Extract _get_exchange_rate() and _save_converted_price() helpers
- Add tests for the new behavior
2026-05-12 13:57:59 +02:00
lukas a4e697a274 Add confirmation before deleting game
Django CI/CD / test (push) Successful in 28s
Django CI/CD / build-and-push (push) Successful in 1m1s
2026-05-12 13:37:55 +02:00
lukas b8187c32b1 Always abandon refunded games
Django CI/CD / test (push) Successful in 36s
Django CI/CD / build-and-push (push) Successful in 54s
2026-05-12 12:49:07 +02:00
lukas bf2b86ba1f Streamline evaluating game status 2026-05-12 12:48:14 +02:00
lukas 913c7d3a98 Scope URLs to the games namespace 2026-05-12 12:43:08 +02:00
lukas 0866eb25e9 update django-ninja to 1.6.2 2026-05-12 11:15:07 +02:00
lukas 39f21bc7db Remove GraphQL API 2026-05-12 11:15:07 +02:00
lukas d9fe99963a Fix htmx_middleware tests 2026-05-12 11:01:48 +02:00