When program-manager approval is off, PM-owned submits stay pending for admin review; notify admins instead of self, align reject messaging, and keep approval history readable after user deletion.
Co-authored-by: Cursor <cursoragent@cursor.com>
Include approvers' own rows in timesheet scope, lock submit/approve/reject/revert, abort unauthorized approve, and split set-password throttles so GET refreshes are not locked out.
Co-authored-by: Cursor <cursoragent@cursor.com>
The activitylog:clean command uses ConfirmableTrait, which in production
blocks on an interactive confirm() prompt. Under cron there is no TTY, so
the prompt resolves to its default (false), the command exits 1, and
ScheduleRunCommand throws a generic Exception on every daily run.
Pass --force so the scheduled task actually runs instead of failing.
Co-Authored-By: Claude <noreply@anthropic.com>
Block submission and approval of timesheets whose week_start Monday has
not arrived yet (a future week). Drafting ahead stays allowed; only the
submit and approve gates close, since hours not yet worked cannot be
verified. The current in-progress week remains submittable.
- Timesheet::isFutureWeek() is the single source of truth
- canUserSubmitTimesheet hides the Submit action for future weeks
- submitTimesheet throws a friendly ValidationException before the 403
- canBeApprovedBy hides Approve/Reject for future weeks
- handleApprove defends with the same ValidationException
- cover with submit + approval gate tests
Co-Authored-By: Claude <noreply@anthropic.com>
The standard Alpine build bundled by Livewire/Filament evaluates directive
expressions via new Function, so the admin UI fails to initialize when
'unsafe-eval' is dropped from script-src. Restore it and drop only the
unused cdn.jsdelivr.net origin (still blocks the "load arbitrary script from
a public CDN" vector). Revert csp_enforce to false (report-only) — enforcing
the current policy can't fully stop XSS while unsafe-inline/unsafe-eval
remain, and full no-eval enforcement needs Livewire's CSP-safe build
(livewire.csp_safe=true) + nonces as a separate rollout. Update the CSP test
to the corrected policy string.
Co-authored-by-by: Claude <noreply@anthropic.com>
Add a comment to TimesheetAccess::scopeTimesheetsForUser noting that the
approvalLogs clause (approvers keep read access to a user's hours on a
project after acting on any of that user's timesheets) is intended, not a
leak to be tightened to the single approved row.
Co-authored-by: Claude <noreply@anthropic.com>
- CSP: drop 'unsafe-eval' and the unused cdn.jsdelivr.net script origin;
enforce by default (SECURITY_CSP_ENFORCE, env-overridable for rollout).
Report-Only stays on so violations surface during transition.
- HSTS: emit when APP_URL is https, not only when the current leg is TLS,
so it survives a TLS-terminating proxy; add preload.
- Cache headers: guard no-store on auth() + GET + text/html instead of the
dead admin/* prefix (panel is mounted at root); uniform X-Frame-Options: DENY.
- Activation email: stop mailing the cleartext temporary password; the
set-password token link is the sole onboarding path.
- Uptime heartbeat: read the token from the X-Uptime-Token header instead
of the query string (leaks via logs/referer); update docs.
- Project aggregates: select only the hour columns to bound memory.
- Password hashing: standardize on the User model's hashed cast; remove
redundant Hash::make in the reset action and the user form.
- Tests: add login-throttle test (5 failed -> 6th throttled); update
CSP/header and uptime/activation tests to the new behavior.
Co-authored-by: Claude <noreply@anthropic.com>
Soft-delete projects with restore support, prevent timesheet cascade loss, and redesign the team members and contributions panels with matched-height scrollable cards.
Co-authored-by: Cursor <cursoragent@cursor.com>
One-time portable (SQLite + Postgres) migration relabeling the audit log
badge column for existing rows; new entries already write the acting role.
Co-Authored-By: Claude <noreply@anthropic.com>
The Audit Log page's "Log" badge always read "admin" regardless of who
acted, because both manual AuditLogger::log and the LogsAuditableChanges
trait hardcoded log_name='admin'. Default to Auth::user()'s role (or
'system' for console/jobs) in both, and the backfill command. Update the
status-change test to expect the employee's role.
Co-Authored-By: Claude <noreply@anthropic.com>
afterStateUpdated callbacks received Filament\Schemas\Components\Utilities\Set,
not the non-existent Filament\Forms\Set — selecting a template/reuse preset
threw a TypeError. Use the correct class.
Co-Authored-By: Claude <noreply@anthropic.com>
Add BroadcastTemplateSeeder with Welcome + Password reset reminder
templates, wired into DatabaseSeeder. Make the Broadcast email modal
stick its header and trim the body textarea so the title stays visible.
Co-Authored-By: Claude <noreply@anthropic.com>
Reusable subject/body pairs managed via a Broadcast templates admin resource
(Filament CRUD, gated to admins and project_admins). The Broadcast email action
gains a "Use a template" picker that fills the subject and body, so common
messages don't need retyping. Complements the existing "Reuse a previous
broadcast" history picker.
Co-Authored-By: Claude <noreply@anthropic.com>
Each broadcast now stores sender, subject, body, and recipient count in a
broadcast_emails table. The Broadcast email action gains a "Reuse a previous
broadcast" dropdown that auto-fills subject and body from a past broadcast, so
admins can re-blast a message without retyping it.
Co-Authored-By: Claude <noreply@anthropic.com>
The email is fixed by the activation token; making it read-only stops users
from changing it and breaking the token match.
Co-Authored-By: Claude <noreply@anthropic.com>
Use Filament\Facades\Filament (the app's convention) instead of the
non-existent Illuminate\Support\Facades\Filament, which threw in the
queue worker and silently failed every activation email. Also correct the
Notification facade import in UserNotifier (should be
Illuminate\Support\Facades\Notification) so the non-queued send path works.
Co-Authored-By: Claude <noreply@anthropic.com>
Email new users their login URL and typed password plus a set-your-own-password
link on creation; add a broadcast action to message all visible users with a
per-user activation link, and a per-row resend action. Adds a public
set-password route backed by the Laravel password broker.
Co-Authored-By: Claude <noreply@anthropic.com>
Add columnSpanFull() to the two-column Flex container so the timesheet view uses the full available width instead of leaving unused horizontal space.
Co-authored-by: Cursor <cursoragent@cursor.com>
Place details, approval history, and notes in the left column and the weekly breakdown in the right column so the view page uses vertical space more effectively.
Co-authored-by: Cursor <cursoragent@cursor.com>
Refresh the lockfile metadata so it matches composer.json after raising the declared runtime to PHP 8.4, avoiding install warnings on staging and production.
Co-authored-by: Cursor <cursoragent@cursor.com>
Scope dashboard pending counts to each approver's assigned projects, harden overtime validation against incomplete weekly arrays, remove legacy PD backfill labels, and declare the PHP 8.4 runtime required by the app.
Co-authored-by: Cursor <cursoragent@cursor.com>
Employees see labeled PM and Program Manager contacts on assigned projects; PMs and program managers can pick employees when staffing projects; weekly-hours print/PDF exports include overtime totals; reports summary cards align consistently.
Co-authored-by: Cursor <cursoragent@cursor.com>
Windows tar archives can leave storage read-only; restore writable storage paths and use route:clear instead of route:cache.
Co-authored-by: Cursor <cursoragent@cursor.com>
Shows per-project overtime rows with live totals on Weekly Hours, and matches the day-strip layout and breakpoints so Friday OT lines up with the planner dates.
Co-authored-by: Cursor <cursoragent@cursor.com>
Return updated buckets from TimesheetSummaryBuilder instead of passing by reference, and filter member options to users not already selected in other repeater rows.
Co-authored-by: Cursor <cursoragent@cursor.com>
Renames Project Director to Program Manager, introduces scoped Project Admin user management, configurable project types, and separate regular/overtime hour tracking in forms, reports, and PDF exports.
Co-authored-by: Cursor <cursoragent@cursor.com>
Traffic counting still matched admin/* after the panel path changed, so today's page views stayed at zero.
Co-authored-by: Cursor <cursoragent@cursor.com>
Allow multiple rows per project per week, auto-approve PM submissions when appropriate, show disambiguated project labels, move the Filament panel to the site root, and expand authorization and test coverage.
Co-authored-by: Cursor <cursoragent@cursor.com>