chore(autonomous-session): consolidate uncommitted work from prior session

Bundles the prior autonomous-session output that was sitting unstaged:

- Em-dash sweep across src/ + tests/ (en-dash/em-dash to hyphen, ~2280 instances)
- country-flag-icons rollout (CountryFlag component, replaces emoji glyphs that
  never rendered on Windows; lazy-loads the 3x2 SVG index as a single chunk
  after the per-subpath dynamic-import approach silently failed in webpack)
- Admin IA Phase 1+2: 7-domain regroup, 41 to 38 pages, /admin/berths index,
  redirects (ocr to ai, reports to dashboard, invitations to users),
  docs/admin-ia-proposal.md
- Per-template email tester (registry + endpoint + UI on Email admin page)
- Cancel-document mode picker (delete-from-Documenso vs keep-for-audit)
- Dashboard PDF report: 25 widgets, SVG charts, date-range picker, 11 resolvers
- Customize-widgets per-region sortables at xl+ (charts/rails/feed); single
  flat sortable below xl when the layout stacks; per-viewport saved orders
- Audit doc updates capturing each shipped item
- Lint fixes: react-compiler immutability in DonutChart (reduce instead of
  let-reassign), set-state-in-effect disables in CountryFlag and
  UploadForSigning preview-bytes effect, unused 'confirm' destructures in
  interest contract + reservation tabs, unescaped apostrophe in test-template
  card copy
This commit is contained in:
2026-05-23 00:52:59 +02:00
parent 43719b49e9
commit 221ae5784e
749 changed files with 7440 additions and 3118 deletions

View File

@@ -37,7 +37,7 @@ function toLocalDatetimeLocal(d: Date): string {
/**
* Build a Date relative to "now" for the quick-pick chips. Day-based
* presets land on the user's preferred time-of-day (`digestTimeOfDay`
* from user_profiles.preferences) same source the default dueAt uses.
* from user_profiles.preferences) - same source the default dueAt uses.
* Hour-based presets use the current time + N hours.
*/
function buildPresetDate(
@@ -142,11 +142,11 @@ function ReminderFormBody({
onSuccess,
}: ReminderFormProps) {
const isEdit = !!reminder;
// Phase 4 load the rep's preferred default-reminder time (HH:MM)
// Phase 4 - load the rep's preferred default-reminder time (HH:MM)
// BEFORE seeding the dueAt state. React Query's cache keeps this
// available synchronously on subsequent dialog opens (staleTime 60s)
// so the initial value is the rep's preference, not the historical
// 09:00 fallback. Enabled only on create-mode opens edit mode
// 09:00 fallback. Enabled only on create-mode opens - edit mode
// already has the existing dueAt to seed from.
const meQuery = useQuery<{ data: { preferences?: { digestTimeOfDay?: string } } }>({
queryKey: ['me', 'preferences'],
@@ -169,7 +169,7 @@ function ReminderFormBody({
const t = new Date();
t.setDate(t.getDate() + 1);
// Honour the rep's user_profiles.preferences.digestTimeOfDay when
// set ("HH:MM"). Falls back to 09:00 historical default.
// set ("HH:MM"). Falls back to 09:00 - historical default.
let h = 9;
let m = 0;
if (userTodPref && /^\d{2}:\d{2}$/.test(userTodPref)) {
@@ -205,6 +205,27 @@ function ReminderFormBody({
});
const users = usersQuery.data?.data ?? [];
// When a client is picked, restrict the YachtPicker to yachts owned by
// that client OR by any company the client belongs to (e.g. a
// managing-director client whose yachts are titled to the company).
// Mirrors the interest-form pattern so the two surfaces stay
// consistent.
const clientDetailQuery = useQuery<{
data: { companies?: Array<{ company: { id: string } }> };
}>({
queryKey: ['client-detail-for-reminder-form', clientId],
queryFn: () => apiFetch(`/api/v1/clients/${clientId}`),
enabled: !!clientId && open,
});
const memberCompanyIds: string[] =
clientDetailQuery.data?.data.companies?.map((m) => m.company.id) ?? [];
const yachtOwnerFilter = clientId
? [
{ type: 'client' as const, id: clientId },
...memberCompanyIds.map((id) => ({ type: 'company' as const, id })),
]
: undefined;
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setError(null);
@@ -391,7 +412,8 @@ function ReminderFormBody({
<YachtPicker
value={yachtId || null}
onChange={(id) => setYachtId(id ?? '')}
placeholder="Search yachts..."
ownerFilter={yachtOwnerFilter}
placeholder={clientId ? 'Yachts linked to client...' : 'Search yachts...'}
/>
</div>
</div>

View File

@@ -8,13 +8,13 @@ import { apiFetch } from '@/lib/api/client';
import { cn } from '@/lib/utils';
/**
* Phase 4 inline reminders list rendered inside an entity's
* Phase 4 - inline reminders list rendered inside an entity's
* Overview tab. Shows the most recent open (pending/snoozed) reminders
* for the linked entity so reps can spot follow-ups without leaving the
* detail page.
*
* Filter is exactly one of clientId / interestId / berthId / yachtId.
* Caller responsibility the listReminders service AND's whichever
* Caller responsibility - the listReminders service AND's whichever
* filters are present, so multiple would intersect rather than union.
*
* No "+ Reminder" button here on purpose: the detail-page header
@@ -51,7 +51,7 @@ const STATUS_ICON: Record<InlineReminder['status'], React.ReactNode> = {
};
interface RemindersInlineProps {
/** Exactly one should be set the entity to filter by. */
/** Exactly one should be set - the entity to filter by. */
clientId?: string;
interestId?: string;
berthId?: string;