CSS transform: rotate(90deg) only rotates the visual; pointer events
still fire in the unrotated viewport coordinate system, and
canvas.getBoundingClientRect returns the rotated bounding box with
swapped width/height. signature_pad computed stroke positions from
clientX/clientY minus that swapped rect, so most strokes landed
outside the canvas drawing buffer. The brief flash of marks visible
when the phone was rotated back to portrait was the small fraction of
points that happened to land inside the canvas, revealed once the CSS
rotation was undone.
Drop the CSS fake-landscape and gate the signature pad behind real
device orientation: portrait shows a rotate-your-phone prompt, and
the pad only renders in landscape where the coordinate system is
clean. Attempt screen.orientation.lock('landscape') where supported;
iOS users with portrait lock see the prompt with a hint.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The inline signature pad on the confirm page was unusable on phones:
the canvas had a fixed 480px internal width but shrank via max-width
on small screens, so pointer coordinates landed in only the top-left
fraction of the drawing buffer. Edge-of-screen strokes also collided
with iOS Safari back-swipe.
Tapping the new signature trigger now opens a full-screen overlay
that rotates to landscape (via 100dvh/100dvw + CSS rotate on portrait
phones, plus an opportunistic screen.orientation.lock) so customers
get the widest possible signing area, away from the system edge.
Also swap the hand-rolled drawing logic for signature_pad, with a
ResizeObserver-driven resize that preserves strokes via
toData/fromData and scales the canvas to devicePixelRatio.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a canvas-based SignaturePad component used on the customer
confirm page. Authorize now requires a non-empty signature; reject
opens a required reason modal. The archived signature is shown to
the customer after confirming and on the admin detail page.
Also fixes the confirm page being clipped at the top when its
content exceeds the viewport: the public layout used
height:100vh + overflow:hidden which cropped the centered card.
Switched to min-height:100vh so tall content can scroll naturally.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Adds a second row of cards (total / pending / closed / rejected)
- New "最近售后工单" table linking to detail pages
- DashboardStats type extended; dashboardApi maps backend overview
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- New /admin/users page (admin only) for creating technicians,
editing role/email, resetting passwords, deleting users
- AftersalesDetail reassign modal now uses a searchable Select
populated from /api/users/assignable instead of raw user ID input
- Menu entry only shown to admins
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>