Spaces:
Sleeping
Sleeping
| // 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 = /<table|DataTable|TableComponent|reportFile/i.test(allCode); | |
| const pass = hasPdf || hasCsv || hasExport || hasTable; | |
| return { pass, | |
| detail: pass | |
| ? "Export trovato: " + [hasPdf && "PDF", hasCsv && "CSV/Excel", hasExport && "exportFn", hasTable && "tabella"].filter(Boolean).join(", ") | |
| : "Manca implementazione report/export (nessun PDF, CSV, exportFn né tabella)" }; | |
| `, | |
| }, | |
| { | |
| requirement: "File dedicato report o componente export", | |
| critical: false, | |
| checkFn: ` | |
| const reportFile = files.find(f => /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 = /<input.*type.*file|type=.file.|multer|fastapi.*UploadFile|storage\\.upload/i.test(allCode); | |
| const hasHandler = /handleUpload|onUpload|uploadFile|file_upload|supabase.*storage/i.test(allCode); | |
| const pass = hasInputFile || hasHandler; | |
| return { pass, | |
| detail: pass | |
| ? "Upload trovato: " + (hasInputFile ? "input[type=file] o multer; " : "") + (hasHandler ? "handler upload" : "") | |
| : "Manca input[type=file], multer, FastAPI UploadFile o storage handler" }; | |
| `, | |
| }, | |
| ], | |
| }, | |
| // ── S411: Inventory / Warehouse ───────────────────────────────────────────── | |
| { | |
| re: /magazzino|inventario|warehouse|stock|giacenza|scorte|prodott.*quantit|quantit.*prodott|inventory/i, | |
| tests: [ | |
| { | |
| requirement: "Modello prodotto/stock con quantità presente", | |
| critical: true, | |
| checkFn: ` | |
| const allCode = files.map(f => 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" }; | |
| `, | |
| }, | |
| ], | |
| }, | |
| ]; | |