New /campaigns section in the ops SPA, gated by manage_users (proxy until a
dedicated manage_campaigns capability is added).
Pages (apps/ops/src/modules/campaigns/pages/):
- CampaignsListPage: table of all campaigns with status chip + progress
bar (sent/total, with fail count), "Nouvelle campagne" + "Éditer le
template" buttons. Empty state with onboarding copy.
- CampaignNewPage: 3-step Quasar Stepper wizard.
Step 1 — upload Map CSV + Giftbit CSV, configure params (name, amount,
commitment_months, sender, throttle, multi-email handling).
Step 2 — preview the matched send list from POST /campaigns/parse, with
counters (matched/unmatched/excluded), per-row match-method
chip, and exclude/include toggle. Banner warns when CSVs are
mis-aligned (leftover gifts or contacts).
Step 3 — confirmation recap with estimated send duration, then fire
POST /campaigns + POST /campaigns/:id/send and redirect to the
live detail page.
- CampaignDetailPage: per-recipient table with status chips updated live
via EventSource on the campaign:<id> SSE topic. Counters bar
(envoyés / cliqués / queued / échecs / non envoyés), progress bar,
per-row customer-link badge with deep-link into /clients/<id>.
Auto-subscribes to SSE when status is draft|sending; "Lancer l'envoi"
button for draft campaigns.
- TemplateEditorPage: GrapesJS-based visual editor for the campaign
templates. Three view modes (Visuel / HTML / Aperçu) — the HTML mode
is the fallback for our table-heavy hand-crafted template that
GrapesJS-preset-newsletter may parse imperfectly. Aperçu mode calls
POST /campaigns/templates/:name/preview on the hub for live variable
substitution. Custom GrapesJS blocks under "Variables" category for
drag-drop insertion of {{firstname}}, {{amount}}, {{gift_url}},
{{description}}, {{expiry}}, {{commitment_months}}. Saves via PUT
with hub-side backup of the previous version.
Wiring:
- api/campaigns.js: hubFetch wrapper, exports parseCsvs / createCampaign
/ listCampaigns / getCampaign / updateCampaign / sendCampaign +
campaignSseUrl(id) for EventSource subscription, + listTemplates /
getTemplate / saveTemplate / previewTemplate for the editor.
- router/index.js: three new routes under /campaigns. The
/campaigns/templates/:name? route is positioned ABOVE /campaigns/:id
to prevent the wildcard from catching template paths.
- config/nav.js + layouts/MainLayout.vue: "Campagnes" sidebar entry with
Lucide Gift icon.
- package.json: grapesjs + grapesjs-preset-newsletter dependencies.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
47 lines
1.2 KiB
JSON
47 lines
1.2 KiB
JSON
{
|
|
"name": "ops-app",
|
|
"version": "0.1.0",
|
|
"description": "Targo Ops — Unified ISP operations platform",
|
|
"productName": "Targo Ops",
|
|
"private": true,
|
|
"scripts": {
|
|
"dev": "quasar dev",
|
|
"build": "quasar build -m pwa",
|
|
"lint": "eslint --ext .js,.vue ./src"
|
|
},
|
|
"dependencies": {
|
|
"@quasar/extras": "^1.16.12",
|
|
"@twilio/voice-sdk": "^2.18.1",
|
|
"chart.js": "^4.5.1",
|
|
"cytoscape": "^3.33.2",
|
|
"grapesjs": "^0.22.16",
|
|
"grapesjs-preset-newsletter": "^1.0.2",
|
|
"idb-keyval": "^6.2.1",
|
|
"lucide-vue-next": "^1.0.0",
|
|
"pinia": "^2.1.7",
|
|
"quasar": "^2.16.10",
|
|
"sip.js": "^0.21.2",
|
|
"vue": "^3.4.21",
|
|
"vue-chartjs": "^5.3.3",
|
|
"vue-router": "^4.3.0",
|
|
"vuedraggable": "^4.1.0"
|
|
},
|
|
"devDependencies": {
|
|
"@quasar/app-vite": "^1.10.0",
|
|
"eslint": "^8.57.0",
|
|
"eslint-plugin-vue": "^9.24.0",
|
|
"sass": "^1.72.0",
|
|
"workbox-build": "7.0.x",
|
|
"workbox-cacheable-response": "7.0.x",
|
|
"workbox-core": "7.0.x",
|
|
"workbox-expiration": "7.0.x",
|
|
"workbox-precaching": "7.0.x",
|
|
"workbox-routing": "7.0.x",
|
|
"workbox-strategies": "7.0.x"
|
|
},
|
|
"engines": {
|
|
"node": "^18 || ^20",
|
|
"npm": ">= 6.13.4"
|
|
}
|
|
}
|