54 Commits

Author SHA1 Message Date
root
cbe06c2d33 Fix Dockerfile: use --no-scripts for cached composer install
composer install needs full app source for post-autoload-dump scripts.
Use --no-scripts in the cached layer, run dump-autoload after full copy.
Build verified locally.
2026-07-14 05:45:36 +02:00
root
2c587cb1fe Optimize Dockerfile: cache composer/npm layers separately
Copy composer.lock and package-lock.json before the full source so
Docker caches the expensive install steps. Only the final COPY layer
changes on code-only commits, making deploys much faster.
2026-07-14 04:56:20 +02:00
root
e6bab80020 Compact files row with collapsible badge and icon-only upload
- No files: minimal 'No files attached' + small 'Attach file' link
- With files: badge 'N files attached' with chevron toggle (Alpine x-data)
- Click badge to expand/collapse file list on surface-1 background
- Upload is an icon-only button (arrow-up-tray) next to the badge
- Existing Livewire wire:model/wire:click backend unchanged
2026-07-14 04:48:10 +02:00
root
8f50b1ccbb Fix attachment upload: read size/mime from stored file, not temp
Livewire temp file getSize()/getMimeType() fails in container volume
environment. Store the file first, then read metadata from the stored
file on the local disk instead.
2026-07-14 03:27:53 +02:00
root
74d8b731b5 Redesign attachment upload with styled drop zone and visual feedback
- Dashed-border upload zone with hover state and upload icon
- Hidden native file input, label triggers file picker
- Loading spinner on Attach button during upload
- Selected file name display with green highlight
- Dark mode support throughout
2026-07-14 03:23:40 +02:00
root
b6acf072b5 Merge branch 'main' of https://github.com/skysyaz/timesheet-staging 2026-07-14 02:29:27 +02:00
root
b4ae8d012e Always show file upload input and auto-save row on attach
- Show the file picker for all editable rows, not just saved ones
- Auto-save the row when Attach is clicked if it has no id yet
- Remove the 'Save the row to attach files' restriction
2026-07-14 02:29:19 +02:00
Claude
ef083fd478
Make container deploy match the native VPS deploy
Pier rebuilds the container on every commit, so the image is now the real
runtime. Bring it to parity with scripts/deploy-staging-vps.sh and make bad
builds fail loudly instead of silently drifting:

- Entrypoint mirrors the VPS runtime steps: migrate --force, config:cache and
  view:cache (routes are NOT cached because the /up closure route can't be
  serialized), favicon generation, and storage permission fixes.
- Run web + queue:work + schedule:work under supervisor in one container.
  Previously nothing processed the database queue and no scheduler ran, so
  jobs and scheduled tasks never executed.
- Declare /app/storage/app as a VOLUME so a Pier persistent volume keeps
  uploaded attachments across rebuilds (they were on ephemeral disk before).
- Add a HEALTHCHECK on the public /login route so Pier can detect a boot that
  never becomes healthy (/up is IP-restricted and unsuitable from loopback).
- composer install now uses --optimize-autoloader, matching the VPS deploy.
- Bake trustProxies into bootstrap/app.php and drop the fragile sed injection
  that silently no-opped if the file was reformatted.
- Add .dockerignore so local .env/vendor/node_modules are never baked into
  image layers and builds stay small.
- Set PHP_CLI_SERVER_WORKERS so artisan serve handles concurrent requests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015qMh7msL8qSXMA9nSLTY38
2026-07-14 00:06:30 +00:00
root
f3952b8d55 Create livewire-tmp dir for file uploads
Livewire stores temporary file uploads in storage/app/private/livewire-tmp.
Without this directory, clicking Attach fails silently.
2026-07-14 01:51:48 +02:00
skysyaz
40d2e97b39
Merge pull request #3 from skysyaz/claude/missing-timesheet-attachments-table-j9rpx6
Run migrations on container startup so schema stays in sync
2026-07-14 07:35:54 +08:00
Claude
252508543c
Run migrations on container startup so schema stays in sync
The container CMD only ran 'php artisan serve', so migrations added to
the codebase (e.g. create_timesheet_attachments_table) were never applied
on the Docker-based staging host. Viewing a timesheet then failed with
'relation "timesheet_attachments" does not exist' because ViewTimesheet
calls $record->attachments()->exists() against a table that was never
created.

Run 'php artisan migrate --force' before serving, matching what the VPS
deploy script already does, and fail fast rather than serving a stale
schema.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015qMh7msL8qSXMA9nSLTY38
2026-07-13 16:00:32 +00:00
skysyaz
17a82994b5
Merge pull request #2 from skysyaz/claude/attachment-upload-weekly-hours-wrxg25
Add file attachments to weekly hours and timesheet view
2026-07-13 23:17:08 +08:00
Claude
02e36d7a9b
Add file attachments to weekly hours and timesheet view
Employees can now attach supporting files (PDFs, images, office docs)
to a project row on the Weekly hours page and view/download them when
viewing a timesheet.

- New timesheet_attachments table + TimesheetAttachment model with
  automatic file cleanup on delete and on parent timesheet deletion
- Weekly hours grid: per-row upload/remove controls for editable saved
  rows, with existing attachments shown as downloadable chips
- ViewTimesheet infolist: new Attachments section with download links
- Authenticated download route authorized via TimesheetAccess so only
  users who can view a timesheet can fetch its files
- Uploads validated for type (pdf/images/doc/xls/csv/txt) and size (10MB)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cwd7t8ySm9Kegxxfefmm69
2026-07-13 15:16:11 +00:00
root
547d56fb78 Harden Dockerfile trustProxies injection and build assets
- Use unescaped sed to inject trustProxies(at: "*") into bootstrap/app.php
- Copy node/npm from node:22-alpine stage and run npm run build
- Remove key:generate from CMD (APP_KEY provided via env)
- Trust proxies so HTTPS URLs are generated behind Traefik
2026-07-13 17:02:50 +02:00
root
da5f5d5770 Fix Dockerfile - install runtime libs separately 2026-07-13 14:47:30 +02:00
root
60dc8d358c Add Dockerfile for containerized deployment 2026-07-13 14:23:43 +02:00
skysyaz
1ab7cde015 Block PM self-finalization and preserve approval audit trails.
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>
2026-07-10 21:32:46 +08:00
skysyaz
eba3535ff3 Fix PM own-timesheet access and harden approval transitions.
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>
2026-07-10 20:56:58 +08:00
skysyaz
b133de92fa Harden security fixes and repair traffic/route regressions.
Accept legacy route:list --columns, lock timesheet edits, tighten password throttle and session defaults, and fix site traffic date/unique races without Postgres-only SQL.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-10 20:52:40 +08:00
b4b7041b8e Harden timesheet authz and close audit/security gaps.
Block weekly-hours IDOR and unauthorized rejects, enforce project membership, stop broadcast reset-token spam, and tighten related hardening plus tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-10 17:51:38 +08:00
skysyaz
5bc545b596
Merge pull request #3 from skysyaz/fix/scheduled-activitylog-clean-force
Fix scheduled activitylog:clean failing in production
2026-07-10 10:22:23 +08:00
0c8d652166 Force scheduled activitylog:clean to skip prod confirm prompt
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>
2026-07-10 10:14:22 +08:00
skysyaz
8f25e9e781 Expand Watchtower reporting with queue and scheduled task hooks.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-09 22:48:10 +08:00
skysyaz
227fb87731 Report unhandled exceptions to Watchtower for centralized error tracking.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-09 22:32:32 +08:00
skysyaz
350306f1e3
Merge pull request #2 from skysyaz/gate-future-week-submission
Gate future-week timesheet submission and approval
2026-07-08 21:58:26 +08:00
skysyaz
4cadac4caa Gate future-week timesheet submission and approval
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>
2026-07-08 21:53:48 +08:00
skysyaz
18aa126cde
Merge pull request #1 from skysyaz/security-audit-fixes
Harden transport/headers and credential handling from security audit
2026-07-08 21:33:17 +08:00
skysyaz
0c807c25b7 Restore unsafe-eval in CSP; revert enforcement to report-only
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>
2026-07-08 21:28:59 +08:00
skysyaz
80d311e746 Document approval-history scope as deliberate business rule
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>
2026-07-08 21:18:35 +08:00
skysyaz
0ff146e0f7 Harden transport/headers and credential handling from security audit
- 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>
2026-07-08 21:16:22 +08:00
fa04d10d7c Fix settings save when clearing optional overtime daily threshold.
Null values violated the settings.value NOT NULL constraint; clearing now deletes the row instead.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-08 10:20:48 +08:00
755fee72ae Protect project timesheets on delete and improve View Project team UI.
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>
2026-07-07 11:25:11 +08:00
skysyaz
2502601c08 Backfill existing audit_log.log_name to causer role
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>
2026-07-03 22:00:04 +08:00
skysyaz
9201184690 Make audit log_name reflect the acting user's role
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>
2026-07-03 21:54:00 +08:00
skysyaz
4568c57148 Fix broadcast modal Set typehint to Filament v5 utility class
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>
2026-07-03 20:55:32 +08:00
skysyaz
d340e74833 Seed Welcome broadcast template and compact broadcast modal
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>
2026-07-03 20:53:48 +08:00
skysyaz
f967857d76 Add broadcast templates
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>
2026-07-03 20:47:27 +08:00
skysyaz
8f39b8e4c7 Record broadcast history and reuse past broadcasts
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>
2026-07-03 20:41:03 +08:00
skysyaz
149bb211e9 Lock email field on set-password page
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>
2026-07-03 20:35:13 +08:00
skysyaz
8fde4eacde Fix Filament facade namespace in activation notification
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>
2026-07-03 20:25:29 +08:00
skysyaz
d33c5f62ca Add user activation email and broadcast feature
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>
2026-07-03 20:07:43 +08:00
29770d3afe Make timesheet view flex layout span full page width.
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>
2026-07-02 17:41:22 +08:00
8e4ca53533 Reorganize timesheet view into a two-column layout.
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>
2026-07-02 17:33:49 +08:00
f2e89acb99 Sync composer.lock with PHP 8.4 platform requirement.
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>
2026-07-02 17:20:59 +08:00
ca4013d888 Align pending approval counts and PHP runtime requirement.
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>
2026-07-02 17:18:14 +08:00
1833279731 Show project approvers on My Projects and fix member assignment scoping.
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>
2026-07-02 16:06:56 +08:00
35462b1d77 Add post-deploy permission fix for tar/rsync deploys on PHP 8.4.
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>
2026-07-02 11:41:38 +08:00
d0f416b32e Include inline OT layout styles so staging works without Vite rebuild.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-02 11:27:25 +08:00
6535c0b4cb Add overtime to Weekly Hours page and align edit-form OT grid with planner.
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>
2026-07-02 11:25:58 +08:00
8f3d56be98 Fix Reports page crash on PHP 8.4 and exclude assigned project members from picker.
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>
2026-07-02 11:04:24 +08:00