QA Report · Pass 1 · PHP Version

SITFD PHP — Laravel Edition — All Roles, All Pages

Date: 2026-08-18 Tester: X2 (browser + curl) App: sitfd-php.production1.jugaar.ai Stack: Laravel + server-rendered + cookies Roles: 9 (POC, Staff, DG, Sec, IT-verify, Approver, Admin, Master, Contact) Pages tested: 30+

Executive Summary

The PHP version is a Laravel server-rendered app at sitfd-php.production1.jugaar.ai. It uses session cookies (XSRF-TOKEN + laravel-session) with one-click demo login via ?login=email in URL. Some bugs are fixed vs. the React version (no trailing 0 in references, working reports, server-side rendered so deep links work). But other bugs are introduced or remain (broken dashboard widget, 500 error on Staff role, no CSP, slow AI).

Status: 2 critical, 4 high, 6 medium, 4 low bugs found. The app is functional but the dashboard is broken, three roles can't see their home page, and AI features are slow with empty results.

2
🔴 Critical
4
🔴 High
6
🟡 Medium
4
🟢 Low
16
All findings

PHP vs React (Vite SPA) Version — Side-by-Side

FeaturePHP (Laravel)React (Vite SPA)
Login flowServer form → 302AJAX + localStorage token
Reference display (table)LAB-2026-000013 (no padding)LAB-2026-0000130 (WITH padding bug)
Dashboard dataBroken (zeros)Working (8 new, 21 acknowledged)
Reports7 types working, real data404 on most types
Public /trackWorks for both refsWorks only for unpadded
Staff dashboard500 "User does not have the right roles"Loads
IT-Verify / Approver dashboard500 same errorLoads
Admin dashboard load0.5s when no Nigraan in cache; 15s when first time0.5s
Ticket detailIncludes History sectionNo history section
AI features (Nigraan)0.4-26s, return advisory in flash0.4-26s, advisory inline
Detail URL pattern/ticket/{ref}/tickets/{id} — different!
Auth deep-linkWorks (session-based)BROKEN (Stale user state)
Security headersNo CSP, no HSTSStrict CSP, X-Frame, X-Content
Help page KB search200 with search inputNo search
One-click login?login=email in URL/tickets?login=email
CSRF419 on stale token (manual refresh needed)Bearer token (no CSRF)
C-1CRITICAL — Dashboard widget query returns 0 for ALL roles
🔴🔴 Critical

Every dashboard renders all-zero stat tiles, but the data exists in the system. Reports page shows 22 rows of real ticket data. The dashboard widget is calling a query that returns 0 (or the wrong table/column).

Reproduced:

Visual evidence: POC sees "0 new (unacknowledged)", "0 awaiting assignment", "0 pending registrations", "0 info requested", "0 incoming 30d", "0 portal", "0 phone" — even though the reports page shows real tickets across all those categories.

Effect: Every authenticated user opens their dashboard and sees a flat "0" page. They have no idea what their workload is. They have to navigate to /tickets or /reports to find anything. This is the most-viewed page of the entire app and it's broken.

Fix: Inspect the dashboard Blade view / DashboardController. The KPI query is returning empty. Likely the scope is wrong (e.g., filtering by user_id when it should be by department_id, or querying a model that doesn't have data seeded).

C-2CRITICAL — Staff, IT-Verify, Approver dashboards 500 "User does not have the right roles"
🔴🔴 Critical

Three roles cannot view their dashboard at all. They get a red error page that says "Something went wrong — User does not have the right roles."

Reproduced fresh login each:

POC, DG Labor, Sec Labor, Admin, Master, Contact all get 200 OK on /dashboard.

Effect: Labor Staff Officers, IT Verification Officers, and Ministry Approvers have no home page. They must navigate to /tickets directly to see their work. This is a broken first impression for 3 of the 9 roles.

Fix: The dashboard controller probably has a role check that excludes dept_staff, it_verification, and ministry_approver roles. Either add support for these roles in the dashboard query, or render a different dashboard layout for them (e.g., for dept_staff, show their assigned tickets; for it_verification, show pending verification queue).

H-1Reference padding bug — FIXED in PHP version (good!)
✅ Fixed

The React version displayed ticket references with a trailing 0 (e.g., LAB-2026-0000130) which broke the public Track feature. The PHP version fixes this.

Verified: Both LAB-2026-000013 AND LAB-2026-0000130 return 200 on the PHP version. The display layer shows the unpadded version LAB-2026-000013, and the URL accepts both. The React version should be patched to match.

H-2Admin dashboard first load takes 15s
🔴 High

Reproduced: First GET /dashboard as admin@sitfd.local takes 14.96s. Subsequent loads are 0.5s. The 15s first load is from the Nigraan AI generation for the dashboard insight text.

Effect: Every time the cache expires, anyone opening the admin dashboard waits 15s. The browser thinks the page is broken. Should be cached more aggressively or rendered asynchronously.

Fix: Cache the Nigel insight for 30+ minutes. Show "Loading..." skeleton while it runs in the background. Or render the page without the insight and stream the insight in via AJAX after page load.

H-3No CSP / HSTS security headers
🔴 High

Findings: Tested 4 URLs (/, /login, /dashboard, /tickets) — none have Content-Security-Policy or Strict-Transport-Security headers.

Present: X-Content-Type-Options: nosniff, X-Frame-Options: SAMEORIGIN, Referrer-Policy: strict-origin-when-cross-origin. These are good but incomplete.

Missing: CSP, HSTS, Permissions-Policy, X-XSS-Protection (legacy).

Effect: Without CSP, an XSS bug in any of the user-input fields (Ticket subject, message, etc.) could become much more dangerous. Without HSTS, downgrade attacks are possible.

Fix: Add to nginx config or middleware: Strict-Transport-Security: max-age=31536000; includeSubDomains and Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'

H-4CSRF token mismatch on stale session (419 errors)
🔴 High

Reproduced: As Master (company), POST to /ticket/LAB-2026-000004/action returns 419 "Page Expired" instead of 403. The CSRF token from the cached page is stale.

Effect: A user opens a ticket detail page, walks away for 30+ minutes, comes back, clicks an action button → 419 error. They have to refresh the page first. This is a common UX failure pattern in Laravel apps.

Fix: Render the action form with a fresh CSRF token on every page load. Or invalidate the session-based CSRF token more aggressively and validate the form token against the user's session token, not the global app token.

M-1Nigraan AI features take 0.4-26s with empty results
🟡 Medium

AI features tested as POC against ticket LAB-2026-000013:

FeatureTimeResult
brief0.4sReturns full structured output (Reference, status, what was complained, what the department tried)
tone21.5sReturns 【Reply tone — AI-generated, advisory】 with NO content
urgency0.4sReturns 302 — no advisory content displayed on ticket page
similar0.4sReturns 302 — content shown after redirect
full-analysis24.7sReturns 302 — slow but works
suggested-reply0.5sReturns 302

Issues:

M-2City field shows wrong data — "Larkana Data Labs" appears in "Sukkur" city
🟡 Medium

Ticket LAB-2026-000004 is from "Larkana Data Labs" (a company from Larkana) but the City column shows "Sukkur".

Effect: Geography is wrong. A Sukkur-based officer might be assigned the ticket because the city matches their location, but the actual company is in Larkana. This affects routing and SLA assignment.

M-3AI advisory content lost after redirect
🟡 Medium

After clicking a Nigraan AI button (e.g., "brief"), the page redirects to /ticket/{ref} but the AI output is not displayed. Only the brief feature showed output (because it returned structured markdown). Tone, urgency, similar, suggested-reply all returned 302 but showed no content after the redirect.

Effect: User clicks "Run tone check" → waits 21s → gets blank page → assumes AI is broken.

Fix: The AI result should be stored in session flash OR passed as a query parameter to the redirect target. Then the redirected page renders the advisory in a blue box at the top.

M-4No notification badge count on API errors
🟡 Medium

The notification bell icon in the topbar shows a badge "2" but only on certain pages. No count consistency.

M-5TypeScript-style labeled buttons don't clarify intent
🟡 Medium

The "Propose resolution (evidence required below)" button assumes the user knows the form interaction. There's no inline hint of what needs to be filled in. A first-time user clicks it and gets a 422 error.

M-6Ticket detail page has no SLA countdown timer
🟡 Medium

The SLA field shows "24 Aug 13:00" — a static date. No countdown. No "X hours remaining" visual. A user can't tell at a glance whether a ticket is approaching breach.

L-1Status text in tables is plain text, not colored pills
🟢 Low

On the ticket list, the "Status" column shows plain text like "new", "in progress", "closed". For comparison, the ticket detail page shows colored pills (blue for "in progress", slate for "urgent", etc.). The list view should match.

L-2No filter for "watched" or "assigned to me" on ticket list
🟢 Low

Only filters: status, type. No "My Queue", "Unassigned", "Watched" filters. A DG Labor with 19 tickets still has to scan them all to find which ones need their action.

L-3Evidence download link not visible in ticket detail
🟢 Low

Evidence list shows "corrective-action.txt resolution 1 KB" but no download link. Clicking the text doesn't open the file. Compare to the React version which has a "Download" button.

L-4Public landing page lacks "company" filter on transparency
🟢 Low

The /transparency page shows aggregate stats but no per-company filter. A company cannot see "how did SITFD handle my industry?"

✅ What works well (PHP version)

How this QA was done

Roles tested (9)

Methods

Artifacts

Phase 1 — Stop the bleeding · 1 week

Fix what users hit on first visit

  1. Critical: Fix the dashboard widget query (C-1). All zeros when there are 191 tickets. Inspect DashboardController or the Blade view — the KPI query is wrong.
  2. Critical: Add Staff/IT-Verify/Approver dashboard (C-2). Three roles get a 500 error. Either render a department-queue dashboard for them, or redirect to /tickets with a filter.
  3. High: Add CSP and HSTS headers (H-3). Required for any production-bound app. Edit nginx config or add middleware.
  4. High: Cache Nigraan insight for 30+ min (H-2). 15s first-load is too slow. Show a "loading" skeleton if the cache misses.
  5. Med: Fix AI advisory display after redirect (M-3). Use session flash or query param to render the output on the redirected page.
  6. Med: Fix tone/urgency empty output (M-1). Either the prompt is wrong or the model output is being truncated.
  7. Med: Fix city field for Larkana Data Labs (M-2). The ticket has city="Sukkur" but company is in Larkana — likely a data import bug.
Phase 2 — UX parity with React version · 1-2 weeks

Bring PHP up to React's standard

  1. Add status pills (color-coded) to the ticket list table — match the detail page
  2. Add filter for "watched" / "assigned to me" / "unassigned" on ticket list
  3. Add download button on evidence list (currently showing filename but no link)
  4. Add SLA countdown timer on ticket detail page
  5. Add assignee column on ticket list — DG Labor wants to see "who's working this"
  6. Add reply-count badge on ticket list rows
  7. Add last-activity timestamp column (replace created_at as the date column)
  8. Add role avatars to client-facing timeline (currently plain text labels)
  9. Add tooltips on disabled buttons
  10. Add per-department transparency page (currently just aggregate)
Phase 3 — Cross-version unification · long-term

Decide React vs PHP route and consolidate

  1. React version wins on: dashboard data, AI advisory display, fast first-time load, no CSRF issues
  2. PHP version wins on: deep links, server-side rendering, no JS-required, working reports, history section, no reference padding bug
  3. Pick one stack and merge the strengths. My recommendation: keep Laravel as the backend, build the React frontend properly with deep-link support and working state management.
  4. Or: keep both and have PHP serve as the public-facing site while React handles the authenticated dashboard experience.

Bottom line

The PHP version is functional for basic ticket workflows and supports deep links. But the dashboard is broken — every user opens their home page and sees zeros. Three roles can't see their home page at all (500 error). The Nigraan AI is slow, sometimes returns empty, and the result is lost after redirect.

Critical priorities: (1) Fix the dashboard widget query. (2) Add Staff/IT-Verify/Approver dashboard. (3) Add CSP and HSTS. (4) Cache Nigraan insight. (5) Display AI advisory on the redirected page.

Comparison with React version: PHP wins on deep-links, history, reports, reference display. React wins on dashboard data, AI display, no CSRF. Neither is production-ready.