Nouvelle fonctionnalité « Événements » (aucun courriel envoyé — moteur d'envoi prêt mais gaté). Hub (services/targo-hub) : - lib/events.js : config événement semée (fete-20-ans, bilingue FR/EN) ; page PUBLIQUE festive GET /rsvp/:id[?t=<jwt>&lang] (n° client · combien · courriel · allergies ; ?t pré-remplit + rattache via magic-link) ; POST /rsvp/:id/submit (résout le client au mieux via portal-auth.lookupCustomer, valide, stocke clé-par- client → re-soumission = mise à jour, pas de doublon) ; endpoints STAFF GET /events, GET /events/:id/rsvps (+ décompte total de personnes), DELETE ; helpers lien perso (generateCustomerToken) + courriel d'invitation bilingue. Réutilise les patrons rating.js. - server.js : dispatch /rsvp + /events → lib/events ; /rsvp/ ajouté à PUBLIC (page + soumission anonymes) ; /events ajouté à ALWAYS_ENFORCE (vue staff = PII → token requis). Ops (apps/ops) : - pages/EventsPage.vue (/evenements) : bandeau événement, KPIs (inscriptions / personnes attendues = total food truck / à vérifier), lien public copiable (affiche/QR/réseaux), carte invitation courriel (envoi gaté), table filtrable (DynamicFilter) + export CSV + suppression. Réutilise PageHeader/StatCard/DataTable/DynamicFilter/useCsvExport. - api/events.js (client hub), route /evenements, entrée menu « Événements » (PartyPopper). Vérifié : 30/30 tests unitaires hub (offline) ; page Ops rend proprement à 375px+desktop, 0 erreur console, dégradation gracieuse tant que le hub n'est pas déployé. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
82 lines
6.1 KiB
JavaScript
82 lines
6.1 KiB
JavaScript
import { route } from 'quasar/wrappers'
|
|
import { createRouter, createWebHashHistory } from 'vue-router'
|
|
|
|
const routes = [
|
|
// Tech mobile view (sent via SMS to field techs)
|
|
{
|
|
path: '/j',
|
|
component: () => import('src/layouts/TechLayout.vue'),
|
|
children: [
|
|
{ path: '', name: 'tech-tasks', component: () => import('src/modules/tech/pages/TechTasksPage.vue') },
|
|
{ path: 'job/:name', name: 'tech-job', component: () => import('src/modules/tech/pages/TechJobDetailPage.vue'), props: true },
|
|
{ path: 'scan', name: 'tech-scan', component: () => import('src/modules/tech/pages/TechScanPage.vue') },
|
|
{ path: 'device/:serial', name: 'tech-device', component: () => import('src/modules/tech/pages/TechDevicePage.vue'), props: true },
|
|
{ path: 'diagnostic', name: 'tech-diag', component: () => import('src/modules/tech/pages/TechDiagnosticPage.vue') },
|
|
{ path: 'more', name: 'tech-more', component: () => import('src/modules/tech/pages/TechMorePage.vue') },
|
|
// Magic link: /j/{jwt-token} — must be LAST to not capture static paths above
|
|
{ path: ':token', name: 'tech-magic', component: () => import('src/modules/tech/pages/TechTasksPage.vue'), props: true },
|
|
],
|
|
},
|
|
// Ops staff desktop view
|
|
{
|
|
path: '/',
|
|
component: () => import('src/layouts/MainLayout.vue'),
|
|
children: [
|
|
{ path: '', component: () => import('src/pages/DashboardPage.vue') },
|
|
{ path: 'clients', component: () => import('src/pages/ClientsPage.vue') },
|
|
{ path: 'clients/:id', component: () => import('src/pages/ClientDetailPage.vue'), props: true },
|
|
{ path: 'tickets', component: () => import('src/pages/TicketsPage.vue') },
|
|
{ path: 'communications', component: () => import('src/pages/CommunicationsPage.vue') },
|
|
{ path: 'communications/c/:token', component: () => import('src/pages/ConversationFullPage.vue'), props: true },
|
|
{ path: 'pipeline', component: () => import('src/pages/PipelinePage.vue') },
|
|
{ path: 'equipe', component: () => import('src/pages/EquipePage.vue') },
|
|
{ path: 'rapports', component: () => import('src/pages/RapportsPage.vue') },
|
|
{ path: 'rapports/revenus', component: () => import('src/pages/ReportRevenuPage.vue') },
|
|
{ path: 'rapports/explorateur-revenus', component: () => import('src/pages/ReportRevenueExplorerPage.vue') },
|
|
{ path: 'rapports/ventes', component: () => import('src/pages/ReportVentesPage.vue') },
|
|
{ path: 'rapports/taxes', component: () => import('src/pages/ReportTaxesPage.vue') },
|
|
{ path: 'rapports/ar', component: () => import('src/pages/ReportARPage.vue') },
|
|
{ path: 'rapports/internet-cher', component: () => import('src/pages/ReportInternetCherPage.vue') },
|
|
{ path: 'rapports/factures-negatives', component: () => import('src/pages/ReportNegativeBillingPage.vue') },
|
|
{ path: 'rapports/resilies-actifs', component: () => import('src/pages/ReportTerminatedActivePage.vue') },
|
|
{ path: 'ocr', component: () => import('src/pages/OcrPage.vue') },
|
|
{ path: 'settings', component: () => import('src/pages/SettingsPage.vue') },
|
|
{ path: 'email-queue', component: () => import('src/pages/EmailQueuePage.vue') },
|
|
{ path: 'factures-fournisseurs', component: () => import('src/pages/SupplierInvoicesPage.vue') },
|
|
{ path: 'sous-traitants', component: () => import('src/pages/SubcontractorsPage.vue') },
|
|
{ path: 'theme', component: () => import('src/pages/ThemePage.vue') },
|
|
{ path: 'facturation/approbations', component: () => import('src/pages/BillingApprovalsPage.vue') },
|
|
{ path: 'telephony', component: () => import('src/pages/TelephonyPage.vue') },
|
|
{ path: 'dispatch', redirect: '/planification' }, // page Dispatch retirée → redirige (anciens signets)
|
|
{ path: 'historique', component: () => import('src/pages/HistoriquePage.vue') },
|
|
{ path: 'evaluations', component: () => import('src/pages/EvaluationsPage.vue') },
|
|
{ path: 'planification', component: () => import('src/pages/PlanificationPage.vue') },
|
|
{ path: 'conformite-adresses', component: () => import('src/pages/AddressConformityPage.vue') },
|
|
{ path: 'rdv', component: () => import('src/pages/RendezVousPage.vue') },
|
|
{ path: 'copilote', component: () => import('src/pages/CopilotePage.vue') },
|
|
{ path: 'agent-flows', component: () => import('src/pages/AgentFlowsPage.vue') },
|
|
{ path: 'taches-pilote', component: () => import('src/pages/TaskGraphPage.vue') },
|
|
{ path: 'network', component: () => import('src/pages/NetworkPage.vue') },
|
|
{ path: 'evenements', component: () => import('src/pages/EventsPage.vue') },
|
|
{ path: 'sync-legacy', component: () => import('src/pages/LegacySyncPage.vue') },
|
|
// Gift campaigns — list, new wizard (CSV upload + matching), per-campaign detail with live SSE updates
|
|
{ path: 'campaigns', component: () => import('src/modules/campaigns/pages/CampaignsListPage.vue') },
|
|
{ path: 'campaigns/new', component: () => import('src/modules/campaigns/pages/CampaignNewPage.vue') },
|
|
// Template editor + gifts inventory routes must be ABOVE /campaigns/:id
|
|
// otherwise the ':id' wildcard captures the literal paths and shows
|
|
// the detail page instead.
|
|
{ path: 'campaigns/templates/:name?', component: () => import('src/modules/campaigns/pages/TemplateEditorPage.vue'), props: true },
|
|
{ path: 'campaigns/gifts', component: () => import('src/modules/campaigns/pages/GiftsInventoryPage.vue') },
|
|
{ path: 'campaigns/pool', component: () => import('src/modules/campaigns/pages/RewardsPoolPage.vue') },
|
|
{ path: 'campaigns/:id', component: () => import('src/modules/campaigns/pages/CampaignDetailPage.vue'), props: true },
|
|
],
|
|
},
|
|
]
|
|
|
|
export default route(function () {
|
|
return createRouter({
|
|
history: createWebHashHistory(process.env.VUE_ROUTER_BASE),
|
|
routes,
|
|
})
|
|
})
|