// acceptancePatternsUI.ts — S-P3-1: split da acceptancePatterns.ts // Sezioni: Roles / RBAC, Report / Export, Search / Filter, File Upload, // Inventory / Warehouse, Notifications, Form Validation, Mobile / PWA import type { GoalPattern } from './acceptancePatternsCore'; export const UI_PATTERNS: GoalPattern[] = [ // ── S411: Roles / RBAC ────────────────────────────────────────────────────── { re: /ruoli?|roles?|rbac|permiss|autorizzaz|permission|access.?control|admin.*panel|superuser|staff/i, tests: [ { requirement: "Sistema ruoli/permessi presente (RBAC)", critical: true, checkFn: ` const allCode = files.map(f => f.content || "").join("\\n"); const hasRoleField = /role|ruolo|permission|is_admin|is_staff|user_role/i.test(allCode); const hasRoleFile = files.some(f => /role|permission|auth.*guard|middleware.*auth/i.test(f.path)); const hasRoleCheck = /hasRole|checkRole|can\\(|cannot\\(|isAdmin|role\\s*===|role\\s*!==|role\\s*===/i.test(allCode); const pass = hasRoleField && (hasRoleFile || hasRoleCheck); return { pass, detail: pass ? "RBAC trovato: campo ruolo + logica verifica presente" : "Manca: " + (!hasRoleField ? "campo role/permission nel modello; " : "") + (!hasRoleFile && !hasRoleCheck ? "logica verifica ruolo (hasRole/middleware auth)" : "") }; `, }, { requirement: "Almeno 2 ruoli distinti definiti", critical: false, checkFn: ` const allCode = files.map(f => f.content || "").join("\\n"); const roleMatches = allCode.match(/['"\`](admin|user|editor|viewer|manager|staff|superuser|moderator|guest)['"\`]/gi) || []; const unique = new Set(roleMatches.map(r => r.toLowerCase())); return { pass: unique.size >= 2, detail: unique.size >= 2 ? "Ruoli trovati: " + [...unique].join(", ") : "Trovati meno di 2 ruoli distinti nel codice" }; `, }, ], }, // ── S411: Report / Export ─────────────────────────────────────────────────── { re: /report|esport.*(?:pdf|csv|excel|xlsx)|export.*(?:pdf|csv|excel|xlsx)|stampa|download.*(?:report|file)|genera.*(?:report|pdf)/i, tests: [ { requirement: "Funzionalità report/export implementata", critical: true, checkFn: ` const allCode = files.map(f => f.content || "").join("\\n"); const hasPdf = /pdf|pdfkit|jspdf|react-pdf|fpdf|reportlab/i.test(allCode); const hasCsv = /csv|xlsx|exceljs|papaparse|to_csv|writecsv/i.test(allCode); const hasExport = /export.*function|exportReport|generateReport|createReport|downloadReport/i.test(allCode); const hasTable = / /report|export|invoice|fattura/i.test(f.path)); return { pass: !!reportFile, detail: reportFile ? reportFile.path : "Nessun file dedicato report/export/invoice trovato" }; `, }, ], }, // ── S411: Search / Filter ─────────────────────────────────────────────────── { re: /cerca|ricerca|search|filtro|filter(?:ing)?|query.*list|lista.*filtr/i, tests: [ { requirement: "Funzionalità ricerca/filtro implementata", critical: true, checkFn: ` const allCode = files.map(f => f.content || "").join("\\n"); const hasSearch = /search|query|filter|ilike|icontains|like.*%|where.*name|fulltext/i.test(allCode); const hasInput = /searchInput|searchQuery|filterValue|searchTerm|\\binput.*search|search.*input/i.test(allCode); const pass = hasSearch && hasInput; return { pass, detail: pass ? "Ricerca: logica query + input trovati" : "Manca: " + (!hasSearch ? "logica query/filtro backend; " : "") + (!hasInput ? "input ricerca frontend" : "") }; `, }, ], }, // ── S411: File Upload ─────────────────────────────────────────────────────── { re: /upload|carica.*file|importa.*file|allegato|attachment|file.*upload|import.*csv|import.*excel/i, tests: [ { requirement: "Funzionalità upload file presente", critical: true, checkFn: ` const allCode = files.map(f => f.content || "").join("\\n"); const hasInputFile = / f.content || "").join("\\n"); const hasQty = /quantity|qty|quantit[àa]|stock|giacenza|scorte/i.test(allCode); const hasProduct = /product|prodott|item|articol|sku|cod.*art/i.test(allCode); const pass = hasQty && hasProduct; return { pass, detail: pass ? "Modello magazzino OK (prodotto + quantità)" : "Manca: " + (!hasProduct ? "campo prodotto/item/sku; " : "") + (!hasQty ? "campo quantity/stock" : "") }; `, }, { requirement: "Operazione movimentazione stock (carico/scarico)", critical: false, checkFn: ` const allCode = files.map(f => f.content || "").join("\\n"); const hasMovement = /carico|scarico|moviment|add.*stock|remove.*stock|update.*quantity|decrease.*stock|increase.*stock|restock/i.test(allCode); return { pass: hasMovement, detail: hasMovement ? "Movimentazione stock trovata" : "Manca operazione carico/scarico/movimentazione" }; `, }, ], }, // ── S411: Notifications ───────────────────────────────────────────────────── { re: /notific|avviso|alert.*send|email.*send|send.*email|webhook|push.?notification|toast.*error|sms/i, tests: [ { requirement: "Sistema notifiche implementato", critical: true, checkFn: ` const allCode = files.map(f => f.content || "").join("\\n"); const hasEmail = /nodemailer|sendgrid|resend|smtp|ses\\.send|send_email|smtplib/i.test(allCode); const hasToast = /toast|sonner|notify|notification|alert.*message/i.test(allCode); const hasWebhook = /webhook|pusher|socket\\.emit|sse.*notify/i.test(allCode); const pass = hasEmail || hasToast || hasWebhook; return { pass, detail: pass ? "Notifiche: " + [hasEmail && "email", hasToast && "toast/UI", hasWebhook && "webhook/realtime"].filter(Boolean).join(", ") : "Manca sistema notifiche (email, toast, webhook o push)" }; `, }, ], }, // ── S411: Form Validation ─────────────────────────────────────────────────── { re: /form.*validaz|validaz.*form|validat.*input|campo.*obbligator|required.*field|zod|yup|react.hook.form|form.*schema/i, tests: [ { requirement: "Validazione form implementata", critical: true, checkFn: ` const allCode = files.map(f => f.content || "").join("\\n"); const hasLib = /zod|yup|joi|valibot|react-hook-form|formik|pydantic/i.test(allCode); const hasValidate = /validate|required|minLength|maxLength|pattern|isEmail|is_email|validators\\.Email/i.test(allCode); const hasError = /errorMessage|fieldError|formError|errors\\.|setError|ValidationError/i.test(allCode); const pass = (hasLib || hasValidate) && hasError; return { pass, detail: pass ? "Validazione trovata: " + (hasLib ? "libreria validazione; " : "validazione manuale; ") + "gestione errori presente" : "Manca: " + (!hasLib && !hasValidate ? "logica validazione; " : "") + (!hasError ? "gestione messaggi errore form" : "") }; `, }, ], }, // ── S411: Mobile / PWA / Responsive ──────────────────────────────────────── { re: /mobile.*first|pwa|responsive|app.*android|app.*ios|flutter|react.native|viewport.*mobile/i, tests: [ { requirement: "Layout responsive / mobile-first presente", critical: true, checkFn: ` const allCode = files.map(f => f.content || "").join("\\n"); const hasViewport = /viewport|meta.*viewport/i.test(allCode); const hasResponsive = /sm:|md:|lg:|xl:|@media|breakpoint|flex|grid.*col/i.test(allCode); const hasManifest = files.some(f => f.path.includes("manifest")); const pass = hasResponsive || hasManifest || hasViewport; return { pass, detail: pass ? "Responsive trovato: " + [hasViewport && "viewport meta", hasResponsive && "breakpoint/Tailwind", hasManifest && "PWA manifest"].filter(Boolean).join(", ") : "Manca viewport meta, breakpoint responsive o manifest PWA" }; `, }, ], }, ];