Просмотр исходного кода

132-B: keten uitgezocht en plan van aanpak toegevoegd

De hele keten nagelopen in een verse export. Oorzaak scherper: zoekterm en
zoekOpen zijn App State en blijven staan, maar de TextEditingController zit in
het component-model en wordt per pagina vers en leeg aangemaakt. Tweede
symptoom van dezelfde oorzaak: de slider blijft verborgen op de nieuwe pagina.

Fix is een binding op het gedeelde FilterBalkComponent (Initial Value naar App
State zoekterm), dus één wijziging voor Home en PUitgaanPage. Builder-stappen,
verwachte export en testscenario staan erbij, plus het alternatief.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
bob 1 день назад
Родитель
Сommit
1a318c3cfd
1 измененных файлов с 208 добавлено и 13 удалено
  1. 208 13
      TASKS.md

+ 208 - 13
TASKS.md

@@ -410,10 +410,66 @@ toont dan exact de 3 jazz-treffers van Amsterdam (nagemeten tegen
 `flutterflowmobiel1?zoek=jazz`), terwijl het zoekveld *Zoek op titel* als
 `flutterflowmobiel1?zoek=jazz`), terwijl het zoekveld *Zoek op titel* als
 placeholder toont. De gebruiker ziet een vrijwel lege pagina zonder oorzaak.
 placeholder toont. De gebruiker ziet een vrijwel lege pagina zonder oorzaak.
 
 
-Fix: *Initial Value* van het TextField binden aan App State `zoekterm` (het veld
-zit achter `if (FFAppState().zoekOpen)` en wordt dus pas gebouwd nadat de state
-gevuld is — de bekende "te laat"-valkuil speelt hier niet). Alternatief: de
-zoekterm wissen bij het verlaten van de pagina.
+#### De keten (nagelopen in een verse export, 2026-09-22)
+
+1. Tik op het vergrootglas → `FFAppState().zoekOpen = !zoekOpen`, de controller
+   wordt gewist, `zoekterm = ''`, dan `herlaadLijsten()`.
+2. Het veld verschijnt (`if (FFAppState().zoekOpen)`) en op Home én `PUitgaanPage`
+   verdwijnt tegelijk de slider (`if (!FFAppState().zoekOpen)`).
+3. Typen → `EasyDebounce` 2000 ms → `FFAppState().zoekterm = controller.text` →
+   `herlaadLijsten()`.
+4. De pagina's geven `zoekterm: FFAppState().zoekterm` door aan hun
+   kaartcomponenten (Home 5×, één per tab; `PUitgaanPage` 1×), en die zetten hem
+   in hun Backend Query als query-parameter `zoek`.
+
+Alles hierin is App State — behalve de `TextEditingController`, en díe zit in het
+**component-model**. Bij een paginawissel wordt dat model vers aangemaakt
+(`_model.textController ??= TextEditingController()`, zonder `text:`), terwijl
+`zoekterm` én `zoekOpen` blijven staan. Vandaar: veld open en leeg, filter actief.
+
+⚠️ Tweede symptoom van dezelfde oorzaak: omdat `zoekOpen` ook meegaat, is op de
+nieuwe pagina óók de **slider verborgen**. Dat maakt het beeld nog verwarrender.
+
+`zoekterm` is **niet** persisted, dus een herstart van de app lost het vanzelf op
+— wat verklaart waarom dit niet eerder opviel.
+
+#### Plan van aanpak — één binding, in het gedeelde component
+
+Zet op `FilterBalkComponent` → het `TextField` → **Initial Value** een binding
+naar App State **`zoekterm`**. Dat is alles: het component wordt door beide
+pagina's gedeeld, dus één wijziging dekt Home en `PUitgaanPage`.
+
+Waarom dit de juiste richting is: het ontwerp is al "de filterbalk is globaal"
+(`zoekOpen` reist immers ook mee). Dan hoort het veld te tónen waarop gefilterd
+wordt, in plaats van de filter stilletjes te verbergen.
+
+De bekende "Initial Value wordt te vroeg gelezen"-valkuil speelt hier **niet**:
+het veld staat achter `if (FFAppState().zoekOpen)` en bestaat dus pas nadat de
+gebruiker het zoeken heeft geopend — op dat moment is de App State al gevuld.
+
+Builder-stappen:
+1. `FilterBalkComponent` openen, het `TextField` in de tree selecteren.
+2. Eigenschap **Initial Value** → klik het kleine **⚏-icoontje naast het label**.
+   ⚠️ Níét het icoontje rechts in die rij — dat is de vertaal-globe, die vervangt
+   het hele rechterpaneel door "Setting Translations" (onschadelijk, klik *Back*).
+3. Set from Variable → zoekveld: typ **`zoek`** (levert `zoekterm`, `zoekActief`
+   en `zoekOpen` op — bewust een term die meer dan één bron overhoudt, anders
+   rendert de optielijst leeg) → App State → **`zoekterm`**. Rendert de rij leeg:
+   hover er eerst vanaf een andere positie naartoe, in dezelfde tool-aanroep.
+4. Confirm.
+
+Verwachte export:
+`_model.textController ??= TextEditingController(text: FFAppState().zoekterm)`.
+
+Testen op een toestel: Home → vergrootglas → zoek `jazz` → menu → Gemeente →
+Uitgaan. Het veld moet nu **`jazz`** tonen bij dezelfde drie treffers. Wis het
+veld → lijst loopt weer vol (reken op de 2 s debounce).
+
+**Alternatief als je liever hebt dat zoeken per scherm is:** zet in plaats
+daarvan op beide pagina's een On-Page-Load-actie die `zoekterm` én `zoekOpen`
+reset. Dat is meer werk (twee pagina's) en je verliest de zoekterm bij elke
+navigatie — maar het is verdedigbaar, want het aanbod verschilt per scherm. Kies
+er één; de combinatie van beide is zinloos.
 
 
 ### 132-C · Stadsrechten publiceren NIET direct — backend moet mee · Eigenaar: Bob (Drupal)
 ### 132-C · Stadsrechten publiceren NIET direct — backend moet mee · Eigenaar: Bob (Drupal)
 
 
@@ -469,16 +525,72 @@ De labels eten de ruimte op die de responsive `Max Lines` veronderstelt. Bij é
 label klopt het wel. Overweeg de labels op één regel te begrenzen, of de
 label klopt het wel. Overweeg de labels op één regel te begrenzen, of de
 omschrijving één regel korter.
 omschrijving één regel korter.
 
 
-### 132-G · Lege lijst op het horeca-overzicht toont alleen een groot logo
+### 132-G · Lege lijst toont een logo i.p.v. een melding · Eigenaar: **Claude — bezig** (onderzoek 2026-09-22 af)
 
 
-Kies je een categorie zonder treffers (in Amsterdam geldt dat voor 9 van de 13
-opties op de tab Activiteiten, want de dropdown toont de **hele taxonomie**, niet
-wat er in die plaats staat), dan verschijnt een kaal `logo800px.png`. De horeca-
-detailpagina doet dit al goed met `LegeLijstComponent` ("Geen evenementen
-gevonden…"). Hergebruik die hier — dat is de goedkoopste fix; het filteren van de
-dropdown op aanwezige categorieën kan niet betrouwbaar (de lijst is gepagineerd).
-Zelfde punt voor de **Bezorgen**-tab van een zaak zonder bezorginfo: die is nu
-volledig leeg.
+Kies je een categorie zonder treffers, dan verschijnt een kaal `logo800px.png`.
+`LegeLijstComponent` (Icon `event_busy` + `Text`) doet dit al goed op `mijnProfiel`.
+
+**Volledige inventaris — 18 plekken in LEVENDE code, 9 bestanden.** De dode
+kopieën (`kanweg/*`, `home_copy`, `home_copy2`, `p_uitgaan_page_copy{,2,3}`,
+`drawer_component_copy`, `mijn_profiel_copy*`, `evenement/event`) bevatten nog
+~15 logo's; geverifieerd wees (alleen zelfreferenties), overslaan.
+
+| Waar | Plekken | Dekt |
+|---|---|---|
+| `HomeUitgaantabelKaartComponent` | 1 | **alle 5 Home-tabs** (één component) |
+| `HomeUitgaanSliderComponent` | 1 | Home-slider (Carousel) |
+| `PUitgaantabelKaartComponent` | 1 | PUitgaanPage-lijst |
+| `PUitgaanSliderComponent` | 1 | PUitgaan-slider (Carousel) |
+| `MijnAanmeldingen` | 1 | staat op `uitgaanskrant.com-applogo.jpg` |
+| `horecagelegenhedenOverzichtCurrent` | 6 | één per tab |
+| `HorecagelegenhedenOverzichtProvinciePage` | 6 | ⚠️ **wél bereikbaar**, `drawer_component_widget.dart:1209` |
+| `thuisBezorgen` | 1 | |
+| `HorecagelegenheidCurrent:248` | 1 | fotocarousel van een zaak — ander geval, beeld-placeholder verdedigbaar |
+
+Daarnaast: **Favorieten tab 1-3 hebben helemáál geen Empty List Widget** (zie
+132-H), en de agenda-tab van een zaak gebruikt een eigen component
+`GeenEvenementenComponentmoetweg` (Icon + vaste tekst) — functioneel al goed,
+opruimkandidaat.
+
+**Carousels:** op #2 en #4 staat de toggle al aan (er staat immers een Image),
+dus alleen *Widget Type* → Component. Reken er toch op dat dit er twee voor Bob
+zijn; `CLAUDE.md` meldt die checkbox als voor Claude onbereikbaar.
+
+#### i18n — onderzocht 2026-09-22, dit is de kern
+
+De `tekst`-parameter is een **letterlijke string**, dus geen globe en geen
+Engelse vertaling. Drie dingen uitgezocht:
+
+1. **`FFLocalizations.getVariableText({nlText, enText})` bestaat al** in
+   `internationalization.dart` — dat is wat FlutterFlow genereert voor een
+   niet-statische tekst. Nergens in gebruik.
+2. ⚠️ **De Global Property `locale` is in dit project ONBRUIKBAAR — altijd
+   `null`.** `MyApp._locale` begint op `null` en `setAppLanguage()` heeft
+   **nul aanroepers** (er is geen taalkiezer). De werkelijke taal komt uit de
+   Localizations-delegate (systeemtaal), en die is in de builder geen bron. Een
+   Visibility-conditie op "huidige taal" werkt hier dus niet. Niet proberen.
+3. Een custom **function** kan er niet bij (geen `BuildContext`); een custom
+   **widget** wel.
+
+**Drie werkbare routes:**
+
+- **D (aanbevolen) — losse componenten per melding.** `Duplicate Component` van
+  `LegeLijstComponent`, `tekst`-parameter eraf, vaste `Text` + globe. Nul
+  condities, per aanroepplek alleen "Empty List Widget → Component → kies".
+  Teksten belanden in `internationalization.dart`, dus de vertaalaudit ziet ze.
+  Kosten: ~10-12 componenten in de lijst.
+- **C — één component met een `soort`-parameter** (String) en N conditionele
+  `Text`-widgets, elk met eigen i18n-sleutel. Houdt de componentenlijst schoon,
+  maar kost 12× een Visibility-conditie met een letterlijke Second Value (het
+  bekende verstopte veld). Het patroon zelf is bewezen: `mijn_aanmeldingen_widget.dart:408`
+  heeft al `if (widget!.type == 'go_out_event')`.
+- **A — custom widget** met `getVariableText(nlText:, enText:)` en twee
+  String-parameters. Minste fragiele klikwerk, maar de teksten staan dan niet in
+  `internationalization.dart` (geen globe, geen audit).
+
+**Openstaand besluit voor Bob: D, C of A.** Reken op ~10-12 unieke teksten en
+22 aanroepplekken (18 hierboven + de 4 bestaande op `mijnProfiel` die van
+`tekst:` af moeten bij route D of C).
 
 
 ### 132-H · Favorieten is uitgelogd een leeg scherm
 ### 132-H · Favorieten is uitgelogd een leeg scherm
 
 
@@ -546,6 +658,89 @@ de gemeenten die je volgt"*) staat er altijd en is de algemene toelichting. Met
 0 gemeenten staan er dus twee bijna gelijke regels onder elkaar — overweeg er één
 0 gemeenten staan er dus twee bijna gelijke regels onder elkaar — overweeg er één
 van te maken.
 van te maken.
 
 
+### 132-L · Favoriete gemeente kiezen springt naar Home i.p.v. naar die gemeente · Eigenaar: **Claude — bezig** (onderzoek 2026-09-22 af)
+
+**Drie plekken, en één ervan doet het expliciet fout:**
+
+| # | Bestand | Nu |
+|---|---|---|
+| 1 | `favorieten_widget.dart:552` | `context.pushNamed(HomeWidget.routeName)` — **navigeert letterlijk naar Home** |
+| 2 | `select_state_drop_down_component_widget.dart:559` | tik op favoriete gemeente → `context.safePop()` |
+| 3 | `select_state_drop_down_component_widget.dart:458` | knop *Toepassen* → `context.safePop()` |
+
+Bij #2 en #3 kom je terug op de pagina waar je vandaan kwam — meestal Home, want
+de keuzepagina wordt geopend vanaf het headerlogo/de drawer. Kwam je van
+`PUitgaanPage`, dan sta je daar mét de **oude** `plaats`-queryparameter: de
+gemeente lijkt gewijzigd (de header leest `gemeenteSelectNaam` uit App State)
+maar de lijst niet.
+
+**Bestemming: `PUitgaanPage` met `plaats = FFAppState().gemeenteSelectId` en
+`services = 'services_3'`** (= *Uitgaan*, het eerste item onder "Gemeente" in de
+drawer; die navigeert al precies zo).
+
+✅ **Gemeten 2026-09-22: een gemeente-tid werkt gewoon als `townid`** — dat was
+het enige echte risico. Amsterdam gemeente `28694` geeft op `services_3` 50 items
+en op `services_4` 5, tegen plaats `28695` respectievelijk 50 en 2. De gemeente
+omvat meer plaatsen, dus dit is zelfs ruimer. Geen Drupal-werk nodig.
+
+**Aanpak per plek:** twee acties achter elkaar — eerst `safePop` (keuzepagina
+sluiten), dan `Navigate To PUitgaanPage`. Backstack wordt dan Home →
+PUitgaanPage, dus de terugknop gedraagt zich normaal. Bij #1 vervangt de
+Navigate gewoon de bestaande `pushNamed(Home)`.
+
+⚠️ **Bijvangst-bug op dezelfde regels:** `favorieten_widget.dart:548` vult
+`provincieSelectName` met `$.parent_nid` in plaats van `$.parent_titel` — dus de
+provincienaam is er een getal. In `select_state_drop_down_component_widget.dart:556`
+staat het wél goed. Meenemen bij deze taak.
+
+### 132-M · Stadseditor-overzicht toont geen ongepubliceerde activiteiten · Eigenaar: nader te bepalen
+
+**Onderzocht 2026-09-22 — het is NIET de status, het is het `komend=1`-filter.**
+
+De lijst *Eigen activiteiten* (tab Stadseditor op `mijnProfiel`, regel 1180)
+roept `MijnAanmeldingenCall` aan met `type: 'activity'`, `komend: '1'`, `limit: 5`.
+
+Wat er goed is:
+- Het endpoint `_custom_mijn_aanmeldingen()` filtert **niet** op status — er staat
+  zelfs expliciet in het commentaar dat status-0 juist mee moet, en het levert
+  `status` + `status_label` ("Wacht op goedkeuring") uit.
+- De app filtert ook niet: `isOngepubliceerd()` wordt alleen gebruikt voor een
+  snackbar bij aantikken (`mijn_profiel_widget.dart:1285`), niet voor zichtbaarheid.
+
+Wat het wél is: bij `komend=1` doet het endpoint een INNER JOIN op
+`field_data_field_date` met `field_date_value >= vandaag`. Gemeten voor **bobcity
+(uid 3621)**:
+
+| nid | status | datum | komt door het filter? |
+|---|---|---|---|
+| 222823 | 0 | 2026-09-26 | ✅ (aangemaakt vandaag 15:03 UTC) |
+| 222818 | 1 | 2026-09-25 | ✅ |
+| 222819 | 0 | 2026-09-20 | ❌ verleden |
+| 214445 | 0 | 2026-09-03 | ❌ verleden |
+
+Dus 2 van de 3 ongepubliceerde vallen weg op **datum**, niet op status — en de
+enige die er wel doorkomt bestond vanochtend nog niet. Vandaar de indruk "alleen
+gepubliceerde".
+
+**Dat het juist ongepubliceerde treft is geen toeval:** die liggen bij de
+redactie, dus hun datum kan verstrijken terwijl ze wachten — en dan verdwijnen ze
+stil uit het overzicht van de indiener. Dat is de echte bug.
+
+**Twee oplossingen:**
+- **App-kant (1 wijziging, geen Drupal):** laat `komend` leeg op déze lijst. Het
+  endpoint sorteert dan op `created DESC` — voor een "eigen activiteiten"-lijstje
+  eigenlijk logischer. Nadeel: verlopen gepubliceerde komen ook mee.
+- **Server-kant (aanbevolen, Eigenaar: Bob):** laat `komend=1` betekenen
+  "toekomstig **óf** nog niet gepubliceerd", in de `if ($komend)`-tak van
+  `custom.mijn_aanmeldingen.inc`:
+  ```php
+  $q->condition(db_or()
+    ->condition('d.field_date_value', gmdate('Y-m-d'), '>=')
+    ->condition('n.status', 0));
+  ```
+  Raakt ook *Mijn evenementen* (`type=go_out_event`), wat daar hetzelfde
+  gewenste effect heeft.
+
 ### 132-K · `&amp;` in de categorie · ✅ GEFIXT OP DEVBOB, productie nog te doen
 ### 132-K · `&amp;` in de categorie · ✅ GEFIXT OP DEVBOB, productie nog te doen
 
 
 **Stand 2026-09-22: Bob heeft beide regels handmatig aangepast op devbob;
 **Stand 2026-09-22: Bob heeft beide regels handmatig aangepast op devbob;