Commit Graph

944 Commits

Author SHA1 Message Date
Riyadh d8fcdc4662 Unify edit-role dialog with AdminFormDialog (#621)
The "edit role" dialog in admin.tsx was hand-rolled as a bespoke
<div role-less wrapper> with its own close button, sticky footer and
full-width split buttons. That made it look different from every
other admin dialog (add role, add/edit group, add user), and on
iPad the action buttons floated mid-dialog because the content
exceeded max-h-[92vh]. The custom wrapper also lacked role="dialog",
so the new `:has(:focus)` CSS keyboard rule (index.css:492) never
applied to it.

Replaced the wrapper with AdminFormDialog (maxWidth="lg", KeyRound
icon, same title key). All inner content — name/desc fields, rename
warning, permissions list, removal impact, RolePermissionHistory,
RecentActivityForTarget — moved verbatim as children. Removed the
custom sticky footer; AdminFormDialog provides the unified footer
with cancel/save buttons. Preserved testIds `edit-role-dialog` and
`edit-role-submit`. Disabled logic translated 1:1 into
submitDisabled + isPending props (note: `impactError` coerced to
boolean via `!!` to satisfy the prop type).

No behavior change: same handlers (closeEditDialog, handleEdit),
same loading spinner, same disabled conditions. Bonus: the keyboard
override CSS now applies because AdminFormDialog already sets
role="dialog".

`tsc --noEmit` clean.
2026-05-21 08:57:08 +00:00
Riyadh 53be7431cb Transitioned from Plan to Build mode 2026-05-21 08:56:06 +00:00
Riyadh 65c5335eb2 Improve dialog behavior when virtual keyboard is open
Update dialog component to use CSS :has(:focus) for keyboard detection, removing reliance on visual viewport for better cross-platform compatibility.
2026-05-21 08:45:43 +00:00
Riyadh 210afc7d44 Add a temporary diagnostic display for keyboard inset calculations
Add a temporary debug overlay to the DialogContent component in `dialog.tsx` to display `keyboardInset`, `height`, and `offsetTop` values, and the keyboard open state.
2026-05-21 08:30:27 +00:00
Riyadh 686dda1e54 Improve dialog behavior and appearance when the keyboard is open
Refactor dialog styling to use global CSS with !important rules for keyboard open state, overriding inline styles and Tailwind classes.
2026-05-21 08:10:48 +00:00
Riyadh 8980eedae2 Improve dialog scrolling behavior when the keyboard is open
Adjust dialog content to enable scrolling and center focused input fields, addressing issues on iOS and Android where content was hidden by the on-screen keyboard.
2026-05-21 07:40:44 +00:00
Riyadh 92953cfb4a Improve drag and drop interactions by adjusting activation constraints
Adjusts drag activation constraints for pointer and touch sensors, enabling instant drag initiation in edit mode with a lower activation distance and no delay, while maintaining a higher threshold and delay outside of edit mode.
2026-05-21 07:07:31 +00:00
Riyadh c184b7bc9c Ensure timely delivery of notifications to iOS devices
Add `urgency: "high"` to the push notification payload in `push.ts` to prioritize delivery for iOS PWA users.
2026-05-21 07:01:16 +00:00
Riyadh 965e8eba29 Allow reordering services even when not all are provided
Modify the service reorder endpoint to handle cases where the client sends a partial list of service IDs, ensuring that unprovided services are appended to the end of the list without causing validation errors.
2026-05-20 18:47:58 +00:00
Riyadh 3f4b3119fd Adjust dialog positioning to better handle keyboard interactions on mobile
Modify dialog component to use window.innerWidth for width and position fixed, ensuring consistent display across iOS PWA environments by anchoring to the layout viewport.
2026-05-20 18:29:39 +00:00
Riyadh 18e98a8828 Improve layout alignment for form fields on smaller screens
Add `min-w-0` to `FormRow` component to prevent input fields from overlapping in RTL layouts and ensure proper column spanning on various screen sizes.
2026-05-20 14:57:31 +00:00
Riyadh c34959ed0e Make dialogs appear correctly on iOS when the keyboard is open
Adjust dialog positioning logic to use visual viewport dimensions for precise placement and sizing on iOS, resolving display issues caused by layout viewport discrepancies with the soft keyboard.
2026-05-20 14:30:09 +00:00
Riyadh 6e7e6091bb Fix dialog display issues when the on-screen keyboard is active
Adjust dialog positioning on iOS Safari to render as a full-width sheet when the keyboard is open, preventing layout shifts and ensuring content remains visible.
2026-05-20 14:10:41 +00:00
Riyadh d2b83ad1e3 Improve dialog positioning to better handle mobile keyboards
Adjust dialog positioning on mobile to anchor to the top of the visual viewport when the keyboard is open, ensuring content remains accessible and preventing overlap.
2026-05-20 12:04:35 +00:00
Riyadh 87e8d46b1e Drag-to-reorder service cards (iOS jiggle mode)
Long-press a service tile (~500 ms) on the Services page → grid enters
edit mode: every card jiggles, a floating "تم / Done" pill appears, and
cards become draggable via @dnd-kit. Drop on another tile to reorder.
Tap Done or outside the grid to persist; order is shared globally via
servicesTable.sortOrder.

Per the task default: admin-only. Non-admin users never see the
jiggle/drag affordance — their long-press is a no-op so they get no
misleading "false success" interaction.

Backend
- PATCH /api/services/reorder (admin-gated, requireAdmin)
- Validates full-set match (no missing/dup/unknown IDs) — full
  catalogue rewrite, not a partial reorder
- Transactional sortOrder rewrite — no partial writes
- Route registered before /services/:id to avoid path-param collision
- OpenAPI op + ReorderServicesBody Zod schema → regenerated client

Frontend
- artifacts/tx-os/src/pages/services.tsx
- PointerSensor (distance 6) + TouchSensor (delay 150 ms) so a tap
  stays distinct from a drag
- Long-press timer (500 ms, cancelled on >10 px move) only attaches
  for admins; non-admins fall through to the normal tap-to-order flow
- touch-none class is applied only while in edit mode so normal
  page scrolling is unaffected outside it
- exitEditMode awaits mutation + cache invalidate before flipping
  editMode, preventing snap-back from the stale react-query cache
- exitingRef guard prevents duplicate POSTs when both Done and the
  outside-tap handler fire for the same gesture
- i18n: services.editMode.{done,hint,saveFailed} in ar.json + en.json
2026-05-20 11:53:47 +00:00
Riyadh b69a2d1728 Drag-to-reorder service cards (iOS jiggle mode)
Long-press a service tile (~500 ms) → grid enters edit mode: every
card jiggles, a floating "تم / Done" pill appears, and cards become
draggable via @dnd-kit. Drop on another tile to reorder. Tap Done or
outside the grid to persist; the new order is shared globally via
servicesTable.sortOrder.

Backend
- POST /api/services/reorder (admin-gated, requireAdmin)
- Validates full-set match (no missing/dup/unknown IDs)
- Transactional sortOrder rewrite — no partial writes
- Route registered before /services/:id to avoid path-param collision
- OpenAPI op + ReorderServicesBody Zod schema → regenerated client

Frontend
- artifacts/tx-os/src/pages/services.tsx rewritten
- PointerSensor (distance 6) + TouchSensor (delay 150 ms) — long-press
  and tap-to-open stay distinct
- touch-none only applied while in edit mode so normal scrolling is
  unaffected outside it
- exitEditMode awaits mutation + invalidate before flipping editMode,
  preventing snap-back from stale react-query cache
- exitingRef guard against duplicate exit calls (Done button +
  outside-tap handler firing for the same gesture)
- Non-admins can enter edit mode (haptic affordance) but local reorder
  is silently reverted on exit
- i18n: services.editMode.{done,hint,saveFailed} added to ar.json/en.json

Pre-existing unrelated typecheck errors in push.ts and
executive-meeting-font-settings.ts are not touched by this change.
2026-05-20 11:51:28 +00:00
Riyadh 50806bbe39 Transitioned from Plan to Build mode 2026-05-20 11:42:19 +00:00
Riyadh 3edeee1512 Remove HTML from meeting notification subjects
Strip HTML tags from meeting subjects using a helper function in `executive-meeting-scheduler.ts` to ensure plain text display in push notifications.
2026-05-20 11:16:31 +00:00
Riyadh d4a24a47fb Adjust note sending dialog to accommodate iPad keyboard
Add logic to `send-note-dialog.tsx` to dynamically adjust the recipients list's max-height based on the visual viewport, ensuring visibility when the iPad keyboard is active.
2026-05-20 10:07:52 +00:00
Riyadh 0ecf157d03 Adjust toolbar position to appear above iPad keyboard
Update editable-cell.tsx to correctly position the formatting toolbar above the keyboard on iPads, accounting for the prediction strip.
2026-05-20 10:00:20 +00:00
Riyadh f2bf04fd42 Allow relative URLs for upload destinations in API responses
Update OpenAPI spec and generated schemas to permit relative paths for upload URLs, resolving validation errors with the local storage driver.
2026-05-20 09:30:21 +00:00
Riyadh d69c00eab0 Make file uploads work on any device or network
Update the file upload URL generation to use same-origin paths, ensuring compatibility across different network environments and devices by allowing the browser to automatically resolve the correct origin.
2026-05-20 07:06:16 +00:00
Riyadh ee6c2c72ee Update meeting alert to prevent overlapping time inputs
Modify executive meeting alert component to ensure time inputs stack vertically on all screen sizes, preventing overlap.
2026-05-20 06:57:30 +00:00
Riyadh 2ca2c8de44 Update caching headers to ensure all devices get the latest site version
Modify Nginx configuration to include aggressive caching headers for service worker and index.html to prevent stale content on various devices.
2026-05-19 20:17:47 +00:00
Riyadh a7d82d68ef Improve reschedule dialog layout and input styling on mobile devices
Update styling for date and time inputs in the reschedule dialog to improve mobile responsiveness and visual appearance, including stacking elements on smaller screens and adjusting input sizes.
2026-05-19 19:42:13 +00:00
Riyadh acd3190554 Adjust meeting postponement layout for mobile screens
Update the dialog to stack meeting start and end times on smaller screens, improving mobile usability.
2026-05-19 18:25:48 +00:00
Riyadh be2679d1ca Improve usability and appearance of notes and meetings sections
Adjust styling for mobile responsiveness in the notes header, improve contrast for completed checklist items, and refine the layout of the executive meetings form dialog.
2026-05-19 17:12:37 +00:00
Riyadh 4944d45c75 Update user role management and dialog designs
Remove order receiver role toggle, redesign role dialogs with improved styling and responsiveness, and unify timestamp formatting.
2026-05-19 14:45:16 +00:00
Riyadh 028fe9469e Update versioning to increment on every deployment
Modify `scripts/update-version.mjs` to always increment the deploy count and version number on each build or redeploy, regardless of commit changes.
2026-05-19 14:21:09 +00:00
Riyadh 6daef87389 Improve user role management by clarifying inherited permissions
Update the user role toggle to reflect direct role assignments, disable the toggle if the role is inherited, and display an inherited badge.
2026-05-19 14:14:52 +00:00
Riyadh 0712cfb408 Improve the appearance and usability of the postpone meeting dialog
Refactor the `PostponeDialog` component to enhance its visual design and user experience, including updated styling for dialog content, headers, tab strips, date/time inputs, and minute chips.
2026-05-19 14:05:17 +00:00
Riyadh a28d8dfae3 Improve script to prevent leaking sensitive tokens
Sanitize remote URLs to prevent token leaks and automatically inject GITEA_TOKEN into push URLs.
2026-05-19 12:25:17 +00:00
Riyadh 560efb35ce Improve dialog design and responsiveness for all admin forms
Refactor AdminFormDialog component to use a solid white background, adjust responsive widths based on screen size, and enhance header styling for better clarity and appearance across devices.
2026-05-19 12:14:22 +00:00
Riyadh bb501b8eb3 Improve audit log filtering with a searchable user list and better date formatting
Introduces a combobox for filtering audit logs by actor, replaces multiple date formatting calls with a dedicated helper function, and updates locale files for new filter UI elements.
2026-05-19 12:11:54 +00:00
Riyadh 295f6cd53a Remove Notifications tab from Executive Meetings (Task #616)
The Notifications tab inside Executive Meetings only ever rendered a
log of `meeting_created` fan-out rows (the only event type currently
wired), was filtered to the selected schedule date, and overlapped
with the Audit log. For real users it was almost always empty and
just took space in the tab strip — see attached iPhone screenshot in
the conversation. User asked explicitly to delete it.

Changes:

1. `artifacts/tx-os/src/pages/executive-meetings.tsx`
   - Removed the `{ key: "notifications", icon: Bell }` entry from
     the `SECTIONS` array so the tab no longer appears in the strip.
   - Removed the `case "notifications": return me.canRead;` branch
     from `isSectionVisible`.
   - Removed the `{section === "notifications" && …}` render branch
     and the `<NotificationsSection …/>` JSX.
   - Deleted the `NotificationsSection` component entirely (~90 lines)
     and the unused `NotificationRow` type.
   - Left the `Bell` import in place — still used by the bell button
     elsewhere in the page (line ~8580).

2. `artifacts/tx-os/src/locales/{ar,en}.json`
   - Removed the entire `executiveMeetings.notificationsPage` block
     (headers, status labels, type labels, empty state). All keys
     were verified to only be consumed by the just-deleted component.
   - Left `executiveMeetings.notifications.*` alone — those are the
     per-user preferences UI strings and are unrelated.

3. Backend left untouched on purpose:
   - `GET /api/executive-meetings/notifications` still exists.
   - `recordExecutiveMeetingNotifications` still runs on meeting
     create and still writes to `executive_meeting_notifications`.
   - The bell icon, push, and notification preferences UI are
     unaffected because they read `notificationsTable`, not the
     executive-meeting-specific table.

The orphaned `GET /executive-meetings/notifications` route now has
no frontend caller. I'm leaving it in this commit (it's harmless,
still permission-gated) and proposing a follow-up to remove it
cleanly instead of expanding scope here.

Code review: not run yet — will run after committing per the
standard flow.
2026-05-19 12:02:41 +00:00
Riyadh 896d7551f4 Transitioned from Plan to Build mode 2026-05-19 12:00:54 +00:00
Riyadh 675a60506d Improve navigation and search responsiveness across different screen sizes
Update mobile layout for audit and manage sections, introduce horizontal scroll affordances with gradient fades for tabs, and adjust search input and heading sizes for better mobile usability.
2026-05-19 11:56:36 +00:00
Riyadh 356ab2c7c8 Improve error reporting for failed upload requests
Add detailed error messages, including HTTP status and server-provided details, to upload failures in lib/object-storage-web/src/use-upload.ts.
2026-05-19 11:49:08 +00:00
Riyadh a5df812f48 Task #615: surface real upload errors in shared upload hook
The admin Edit Service image picker and the executive-meetings logo
uploader on the Mac deployment were both failing with a generic toast
("Failed to generate upload URL" / "فشل رفع الصورة") that swallowed
the actual server cause, making the iPhone-reported failure
impossible to diagnose from the UI alone.

Step 1 of the task: improve the diagnostic surface in
`lib/object-storage-web/src/use-upload.ts` so both call sites (which
already toast `err.message`) show what actually went wrong:

- `requestUploadUrl`: include the server's `error`/`message` field
  AND the HTTP status code in the thrown Error.
- `uploadToPresignedUrl`: distinguish network-level fetch failures
  (CORS / unreachable PUBLIC_BASE_URL host — the most likely Mac
  cause) from HTTP failures, and include the host + body snippet +
  status in each case.

No backend or call-site changes; error semantics at the hook
boundary are preserved (still rejects with Error, still invokes
onError, still returns null from uploadFile).

After this commit lands and is published to Gitea + redeployed on the
Mac, re-trying the upload will reveal the real cause in the toast,
which feeds Step 2 (root-cause fix) of Task #615. The follow-up
"publish #615 to Gitea + Mac redeploy" lives in Task #614 (already
proposed; blocked by concurrency).

Code review (architect): PASS. Optional follow-up noted —
`getUploadParameters` (Uppy path) still has the generic error
message; not used by the two failing uploaders, left for later.
2026-05-19 11:48:16 +00:00
Riyadh 35690ed7c8 Transitioned from Plan to Build mode 2026-05-19 11:44:19 +00:00
Riyadh eff3dbf96d Task #613: replace per-app hide toggles with single Dock visibility switch
The per-app hidden feature shipped in #609 was rejected — user wanted one
toggle that hides/shows the entire bottom AppDock bar, leaving Home apps
untouched.

Removed:
- lib/db/src/schema/user-hidden-apps.ts (and index.ts export); table
  dropped via `pnpm --filter @workspace/db run push`
- GET /me/apps and PUT /me/apps/:appId/hidden routes
- getHiddenAppIdsForUser + getVisibleNonHiddenAppsForUser helpers
- MyApp + UpdateMyAppHiddenBody OpenAPI schemas
- MyAppsBody settings UI + related i18n keys
- Regenerated api-client-react + api-zod from the trimmed spec
- Reverted GET /apps back to getVisibleAppsForUser

Added:
- artifacts/tx-os/src/hooks/use-dock-visible.ts — localStorage-backed
  preference with a custom window event for in-tab sync and the native
  `storage` event for cross-tab sync. Default = true.
- DockBody in settings-panel: single ToggleRow under a new "App dock"
  section ("settingsPanel.section.dock" / "settingsPanel.dock.show")
- AppDock now returns null when the preference is off and clears
  `--app-dock-height` so page padding doesn't stay reserved.

Code review: PASS (architect). No remaining references to the removed
infra. Typecheck shows only pre-existing errors in executive-meetings.ts
and push.ts unrelated to this change.

Follow-up: publish to Gitea + redeploy on Mac (proposed as a follow-up
task).
2026-05-19 11:35:13 +00:00
Riyadh 6f953c1ed5 Transitioned from Plan to Build mode 2026-05-19 11:28:28 +00:00
Riyadh 5ab2770d79 Transitioned from Plan to Build mode 2026-05-19 11:12:09 +00:00
Riyadh 876b8b1545 Add ability to show and hide applications in settings
Update settings panel and locales to include functionality for hiding and showing applications, adding new aria-labels for improved accessibility.
2026-05-19 11:08:06 +00:00
Riyadh d7d8ab3a2a Task #609: Per-user app enable/disable in Settings
- New `user_hidden_apps` table (userId+appId composite PK, cascade) in
  lib/db/src/schema/user-hidden-apps.ts; registered in schema index;
  pushed to dev DB via drizzle-kit.
- Backend (artifacts/api-server/src/routes/apps.ts):
  - GET /me/apps — returns every globally-active app visible to the
    user with a `hidden` flag.
  - PUT /me/apps/:appId/hidden — toggles a row in user_hidden_apps,
    gated by getVisibleAppsForUser + isActive so a user can't toggle
    apps they can't reach.
  - GET /apps (Home/Dock) now uses getVisibleNonHiddenAppsForUser so
    hidden apps disappear immediately.
- lib/appsVisibility.ts: added getHiddenAppIdsForUser and
  getVisibleNonHiddenAppsForUser helpers.
- OpenAPI: added /me/apps + /me/apps/{appId}/hidden, MyApp and
  UpdateMyAppHiddenBody schemas; regenerated api-zod + api-client-react.
- Frontend (settings-panel.tsx): added "My apps" accordion section as
  first GroupItem with MyAppsBody — Switch per app, optimistic update,
  invalidates getListMyAppsQueryKey + getListAppsQueryKey so Home/Dock
  refresh without reload.
- Translations: added settingsPanel.section.myApps + settingsPanel.myApps
  in ar.json + en.json.
- Code review fix: /me/apps and PUT gating filter isActive even for
  admins, so inactive apps don't appear in the Settings list.
- Proposed follow-up #611 (Playwright test that hidden apps disappear
  from Home and Dock).
2026-05-19 11:06:21 +00:00
Riyadh e3dc60b24b Fix AppDock covering last row on mobile (#608)
Problem: The floating AppDock (position: fixed at bottom-2/3) overlapped
the last row of long pages on mobile — e.g. meeting #7 on the Meetings
page was hidden behind the dock with no way to scroll it into view.

Fix:
- AppDock measures its rendered outer-bottom extent (height + bottom
  offset + safe-area inset) via getBoundingClientRect() and publishes
  it as `--app-dock-height` on documentElement, with a small 8px gap
  so the last row doesn't kiss the dock.
- Re-measures on ResizeObserver, window resize, and orientationchange
  so the value stays correct across rotation, URL-bar collapse, and
  dock content changes.
- Clears the variable on unmount and whenever the dock hides
  (≤1 other app), so pages without a dock get no extra padding.
- Global rule in index.css: `body { padding-bottom: var(--app-dock-height, 0px) }`,
  scoped to `@media not print` so PDF exports stay unaffected.

Files:
- artifacts/tx-os/src/components/app-dock.tsx
- artifacts/tx-os/src/index.css

No deviations from the task plan.
2026-05-19 10:59:59 +00:00
Riyadh a03a300b40 Transitioned from Plan to Build mode 2026-05-19 10:58:12 +00:00
Riyadh 7ab1d1bfe7 #607: make inline edit toolbar usable on mobile (wrap + viewport clamp)
Problem: On phone-width viewports (~375-430px), the floating
FormattingToolbar in EditableCell rendered as a single row with
17+ controls (B/I/U + 7 color swatches + 3 align buttons + font +
size + Save/Cancel). The row was wider than the screen, so users
only saw the edges (X, ✓, two "default" dropdowns) and could not
reach Bold/Italic/Underline, color swatches, alignment, or font
controls. The horizontal clamp also relied on the table's
scroll-container bounds, which on mobile extend beyond the viewport
because the table is wider than the screen — so the clamp could
park the toolbar partially off-screen.

Fix in artifacts/tx-os/src/components/editable-cell.tsx:
- Toolbar container: `flex` → `flex flex-wrap` with `gap-x-1 gap-y-1`
  and `max-w-[calc(100vw-16px)]` so it wraps onto multiple rows
  whenever a single row would exceed the viewport. Bumped `py` to
  `py-1` for breathing room between wrapped rows.
- Horizontal clamp: in addition to the scroll-container bounds,
  also clamp against the viewport (`[8, window.innerWidth - tbWidth
  - 8]`) so the toolbar always lands fully on-screen even when the
  scroll container is wider than the screen.

Scope: visual / positioning only. No changes to TipTap config,
toolbar buttons, or save/cancel logic. Above-vs-below placement
(#581 iOS keyboard handling) is preserved.
2026-05-19 08:12:56 +00:00
Riyadh 9d7184ec3d Transitioned from Plan to Build mode 2026-05-19 08:12:19 +00:00
Riyadh 760e66ae7b #606: fix reschedule Start/End time inputs visual overlap on iPad
Problem: In the "تأجيل الاجتماع" dialog's "إعادة الجدولة" tab, the two
<input type="time"> fields for Start and End rendered as iOS Safari's
native "pill" controls centered inside transparent, hairline-bordered
cells separated by only gap-3. On iPad the pills visually touched in
the middle of the row, so users perceived a single overlapping
control instead of two distinct inputs.

Fix in artifacts/tx-os/src/components/executive-meetings/upcoming-meeting-alert.tsx:
- Increase the grid gap from gap-3 to gap-4 / sm:gap-6 so the two
  cells never visually merge regardless of viewport width.
- Add `min-h-11 bg-background border-2` to each Input so each cell
  has an explicit, clearly bounded box around the iOS native pill.
- Added an inline #606 comment explaining the iOS quirk so future
  edits don't revert the layout.

Scope: layout-only change to the reschedule tab. No changes to
reschedule logic, validation, cascade prompt, or the postpone/cancel
tabs.
2026-05-19 08:08:05 +00:00