ソースを参照

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 13 時間 前
コミット
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
 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)
 
@@ -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
 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
 
@@ -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
 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
 
 **Stand 2026-09-22: Bob heeft beide regels handmatig aangepast op devbob;