gigafibre-fsm/apps/ops/src
louispaulb 8410464a22 feat(campaigns/reminder): cascade clicks to parent + family banner
A reminder campaign is a deep-copy of its parent's non-clicked
recipients with NEW gift_tokens. Clicks on the reminder were flagging
the CHILD recipient's gift_link_clicked but the parent campaign's
counters never updated — operators had to check two campaigns to see
the cumulative click rate.

Hub:
- New cascadeClickToParent() helper — when a recipient with
  parent_campaign_id is flagged as gift_link_clicked, mirror the flag
  + timestamp onto parent.recipients[parent_row_index] and broadcast
  a recipient-update SSE event so the parent's open page refreshes
  live. Adds a gift_clicked_via_reminder breadcrumb (the child
  campaign id) so the parent UI can show "↩ via la relance XXX".
  Idempotent — already-clicked rows are no-op.
- Three cascade call sites: applyWebhookEvent fast path (CustomID),
  applyWebhookEvent fallback (msgId scan), handleGiftRedirect wrapper.
- handleGiftRedirect also now sets gift_link_clicked=true on first
  successful redirect (Mailjet webhook can lag or drop; the wrapper
  redirect is the most reliable click signal we have).
- GET /campaigns/:id now attaches a "reminders" array with summary
  counters for every reminder child of the campaign.

Ops UI:
- "Cette campagne est une relance" banner on child detail pages with
  a back-link to the parent.
- "N relance(s) envoyée(s)" banner on parent detail pages with
  clickable chips showing each child's gift_clicked/total ratio.
- Recipient table: 🔁 icon next to the gift-click indicator when the
  click came via reminder, plus a "↩ via la relance XXX" line in the
  tooltip so the operator can trace the engagement channel.

One-time backfill applied on prod to mirror clicks that happened
between reminder send and this deploy (1 click cascaded —
cmp-20260522-2d4605 gift_clicked 27 → 28).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 15:24:52 -04:00
..
api feat(campaigns): reminder campaign for non-clickers 2026-06-01 11:43:35 -04:00
boot feat: add ops app + CONTEXT.md, simplify URL to /ops/ 2026-03-30 22:41:58 -04:00
components feat(ops): Service Contract detail view + sub-delete redirects there 2026-04-23 14:46:34 -04:00
composables fix(ops/client): cancelled subs no longer inflate monthly total + Lieu link in-app 2026-05-08 11:21:18 -04:00
config feat(ops/campaigns): UI module for gift campaigns + GrapesJS template editor 2026-05-21 19:08:04 -04:00
css feat: contract → chain → subscription → prorated invoice lifecycle + tech group claim 2026-04-22 20:40:54 -04:00
data docs: reorganize into architecture/features/reference/archive folders 2026-04-22 11:51:33 -04:00
layouts feat(ops/campaigns): UI module for gift campaigns + GrapesJS template editor 2026-05-21 19:08:04 -04:00
modules feat(campaigns/reminder): cascade clicks to parent + family banner 2026-06-01 15:24:52 -04:00
pages fix(ops/client): cancelled subs no longer inflate monthly total + Lieu link in-app 2026-05-08 11:21:18 -04:00
router feat(campaigns): gifts inventory page + expiry presets 2026-05-22 10:21:05 -04:00
stores fix(ops/dispatch): surface customer + service-location links from a job + fix bad coords 2026-05-08 10:29:59 -04:00
utils refactor: extract composables from 5 largest files — net -1950 lines from main components 2026-04-08 17:57:24 -04:00
App.vue feat: add ops app + CONTEXT.md, simplify URL to /ops/ 2026-03-30 22:41:58 -04:00