docs(08): capture phase context
This commit is contained in:
parent
a394517603
commit
1459b86a9d
2 changed files with 319 additions and 0 deletions
167
.planning/phases/08-engagement-user-profile-new/08-CONTEXT.md
Normal file
167
.planning/phases/08-engagement-user-profile-new/08-CONTEXT.md
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
# Phase 8: Engagement User Profile (NEW) - Context
|
||||
|
||||
**Gathered:** 2026-05-07
|
||||
**Status:** Ready for planning
|
||||
|
||||
<domain>
|
||||
## Phase Boundary
|
||||
|
||||
A real, shareable per-employee profile page at `/mobile/engagement/[userId]` rendering identity → period selector → 4 hero metrics (2×2 grid) → categorized activity breakdown → two recent-items sections (time entries + meetings). Sourced from existing engagement endpoints (no new data). Replaces the desktop user-detail modal pattern on mobile only — desktop stays as-is.
|
||||
|
||||
Scope anchor (from ROADMAP.md): tapping an Engagement overview row navigates to `/mobile/engagement/[userId]`; the profile is a real page so the device back gesture returns to the overview at the same scroll position; layout is single-column per ENG-06; reuses existing endpoints per ENG-08.
|
||||
|
||||
</domain>
|
||||
|
||||
<decisions>
|
||||
## Implementation Decisions
|
||||
|
||||
### Route, segment, and shell integration
|
||||
- **D-01:** New page at `app/mobile/engagement/[userId]/page.tsx`. The `EngagementUserRow` component (Phase 7, `components/mobile/EngagementUserRow.tsx`) already renders an entire-row `Link` to `/mobile/engagement/[graphUserId]` per Phase 7 D-19 — Phase 8 owns the destination. **Do NOT modify `EngagementUserRow.tsx`.**
|
||||
- **D-02:** `'use client'` + `useState` + `useEffect` + `fetch` (CLAUDE.md: no SWR/react-query, match Phase 7 pattern).
|
||||
- **D-03:** `[userId]` in the route segment is the `graph_users.id` (UUID-like string from Microsoft Graph), matching the existing `/api/engagement/user/[userId]` endpoint contract. Not the Better Auth `user.id`. Same convention used by Phase 7's `EngagementUserRow.graphUserId`.
|
||||
- **D-04:** Scroll restoration to overview: rely on Next.js App Router's default `scrollRestoration: true` — `Link` prefetch + browser back/forward restores scroll position automatically. No `sessionStorage` workaround needed unless the planner discovers the default doesn't hold. Treat as "verify in execution; if broken, then mitigate."
|
||||
|
||||
### Identity header
|
||||
- **D-05:** Avatar source — Microsoft Graph photo with **initials fallback** when no photo is available. Use the existing `getMsgraphClient()` factory and a server-side photo fetch through a new thin endpoint (e.g. `/api/mobile/engagement/user/[userId]/photo` returning a small JPEG or 404). Initials computed via Phase 7's exported `getInitials(displayName)` helper from `EngagementUserRow.tsx`.
|
||||
- **D-06:** Fields under name (in this stacked order):
|
||||
1. **Job title** — `graph_users.job_title`
|
||||
2. **Department** — `graph_users.department` (omit row if NULL)
|
||||
3. **Email** — `graph_users.email`, rendered as `mailto:` link
|
||||
4. **Last active** — most recent of (`time_entries.entry_date`, last `engagement_snapshots` activity timestamp), formatted via `useUserTimezone()`. Display as relative if ≤7 days ("2 hours ago"), absolute if older ("2026-04-15"). Omit row if no signal.
|
||||
- **D-07:** Header card uses `Card` + `CardContent` with horizontal layout: avatar (left, ~56px) + identity stack (right). No subtitle, no role badge — the four rows above carry sufficient identity weight on a phone.
|
||||
|
||||
### Period selector
|
||||
- **D-08:** Reuse Phase 7's `EngagementPeriodChips` component as-is. 3 chips: `7d` / `30d` / `90d` mapping to `D7`/`D30`/`D90`.
|
||||
- **D-09:** **Default period = `D30`** (matches Phase 7 overview default, preserves user's mental model carried from the previous screen).
|
||||
- **D-10:** Sticky behavior: same as Phase 7 — `sticky top-0 z-10 bg-background pt-2 pb-3 -mx-4 px-4` so chips run edge-to-edge while metrics scroll above. Header card scrolls under the chips.
|
||||
|
||||
### Key metrics — 2×2 grid (4 hero metrics)
|
||||
- **D-11:** **Layout:** 2×2 grid of `Card` + `CardContent`, mirroring Phase 3 dashboard KPI grid. Each card: big number (`text-2xl font-semibold`), small label (`text-xs text-muted-foreground`), optional inline qualifier. `gap-3` between cards, no shadows (matches Phase 7 D-10).
|
||||
- **D-12:** **Hero metrics** (in reading order: top-left, top-right, bottom-left, bottom-right):
|
||||
1. **Hours worked** — sum of `time_entries.hours_worked` over the selected period
|
||||
2. **Billable hours** — sum where `billable = true`
|
||||
3. **Days worked** — distinct `entry_date` count
|
||||
4. **Meetings attended** — `teams_meetings_attended` summed over the period from `engagement_snapshots`
|
||||
- **D-13:** When the value is `0` or `NULL`, render `0` (not `—`). When the user has no activity at all, render the cards with zeros — empty-state messaging belongs at the recent-items section, not the metric grid.
|
||||
|
||||
### Activity breakdown — categorized rows
|
||||
- **D-14:** Below the metric grid, a **single `Card`** with three labeled subsections in this order:
|
||||
1. **Time** — Hours worked / Billable hours / Days worked / utilization% (billable ÷ hours, if applicable)
|
||||
2. **Communication** — Teams messages (chat + private summed) / Emails sent / **After-hours: X% of messages, Y% of meetings** (the after-hours signal lives here, per D-15)
|
||||
3. **Meetings** — Meetings attended / Meetings organized / Total meeting duration (hours, from `meeting_duration_seconds`) / Zoom calls (only if `zoom` block in the response is non-null)
|
||||
- **D-15:** **After-hours** signal — single row inside Communication: `After-hours · {messagesPct}% messages, {meetingsPct}% meetings`. Tucked in, not callout-styled. Hide row when both are 0%.
|
||||
- **D-16:** Each subsection is a label (`text-sm font-medium text-muted-foreground`) followed by metric rows (label left, value right, `flex justify-between text-sm py-1.5`). No charts, no sparklines — keeps render light and matches DASH-04 / Phase 7 §6.5 ("no multi-series chart on mobile").
|
||||
- **D-17:** Hide a row entirely when its underlying field is `null` or `0` AND it's a "presence" signal (e.g. zoom calls when zoom not configured). For first-class metrics (hours, meetings) always render with `0`.
|
||||
|
||||
### Recent items — two separate sections, tap-to-expand
|
||||
- **D-18:** Two separate sections rendered in this order (after activity breakdown):
|
||||
1. **Recent time entries** — last 10 from `recentEntries` (existing endpoint already returns these), sorted by `entry_date` descending. Each row collapsed shows: `entry_date` (formatted via `useUserTimezone()`), `hours_worked`, billable badge if applicable, ticket/project ref (if present), one-line notes preview. **Tap expands inline** to reveal: full notes, ticket title (if available), full project ref, exact timestamp.
|
||||
2. **Recent meetings** — last 10 from `recentTeamsMeetings`, sorted by `date` descending. Each row collapsed: subject, date (TZ-formatted), duration (from `meetingMins`), attendee count if available. **Tap expands inline** to reveal: matched time entries (the existing `matchedEntries` array), Zoom call linkage (if present), organizer.
|
||||
- **D-19:** **Bound to 10 each** (count-bounded, not date-bounded). Period selector does NOT affect recent-items count — it remains 10/10 regardless of D7/D30/D90. (Period changes the metrics + breakdown only.)
|
||||
- **D-20:** **Tap-to-expand mechanism:** local component state (`Set<string>` of expanded entry IDs / meeting IDs). No URL state, no router push. Expanded rows animate via Tailwind `transition-all` + height; collapsed by default. Reuse shadcn `Collapsible` if it fits cleanly, else hand-roll.
|
||||
- **D-21:** Empty states — when `recentEntries` is empty: show "No time entries in the last 30 days" inline (one row). Same for meetings. Section header still renders.
|
||||
|
||||
### Data fetching
|
||||
- **D-22:** Reuse `/api/engagement/user/[userId]?period={D7|D30|D90}` as-is per ENG-08. The endpoint already returns `user`, `hours`, `recentEntries`, `recentTeamsMeetings`, `dailyActivity`, `zoom`, `afterHours`, `peerMax` — Phase 8 ignores `peerMax` (radar/peer comparison is a desktop-only flourish) and `dailyActivity` (we render via metrics, no chart).
|
||||
- **D-23:** Single fetch on mount + on period change. Loading skeleton matches Phase 7 pattern: header skeleton + 4 metric-card skeletons + breakdown card skeleton + 2 recent-list skeletons.
|
||||
- **D-24:** Error handling: if 404 → "User not found" empty page with back link to `/mobile/engagement`. If 500 → toast (sonner) + retry button on the page body.
|
||||
|
||||
### Avatar/photo endpoint
|
||||
- **D-25:** New thin route `/api/mobile/engagement/user/[userId]/photo` (server-side) — calls Microsoft Graph `/users/{id}/photo/$value` via `getMsgraphClient()`, returns the binary or 404. `requireAuth()` first. Cache headers: `Cache-Control: private, max-age=3600`. Browser caches the photo per-tab. Fallback to initials happens client-side when the `<img>` errors out.
|
||||
- **D-26:** Photo fetch is best-effort. If `MSGRAPH_*` env not configured, the endpoint returns 503 — the client treats any non-200 as "use initials." Phase 8 doesn't gate on Graph being configured.
|
||||
|
||||
### Claude's Discretion
|
||||
- Exact card/row spacing, typography weights within Phase 7's established tokens (`text-2xl`, `text-xs`, `text-sm`, `space-y-3`, `gap-3`)
|
||||
- Whether to use `Collapsible` from shadcn or a hand-rolled disclosure for D-20
|
||||
- Skeleton component composition (use Phase 7 shapes as reference)
|
||||
- Toast wording for the 500 error case (D-24)
|
||||
- Whether to memoize the expand-state `Set` or use a plain object — implementation detail
|
||||
- The exact threshold for "Last active" relative-vs-absolute (D-06): treat ≤7d as relative as a starting heuristic; planner can refine
|
||||
|
||||
</decisions>
|
||||
|
||||
<canonical_refs>
|
||||
## Canonical References
|
||||
|
||||
**Downstream agents MUST read these before planning or implementing.**
|
||||
|
||||
### Phase scope and requirements
|
||||
- `.planning/ROADMAP.md` §"Phase 8" — goal, depends-on, success criteria
|
||||
- `.planning/REQUIREMENTS.md` ENG-06, ENG-07, ENG-08 — single-column layout, real-page-not-modal, reuse existing endpoints
|
||||
|
||||
### Prior-phase context this builds on
|
||||
- `.planning/phases/07-engagement-overview-new/07-CONTEXT.md` — period chip mapping (D-04..07), summary card visual tokens (D-10), sparkline pattern, list patterns; **D-19 establishes the row→`/mobile/engagement/[graphUserId]` link**
|
||||
- `.planning/phases/07.1-user-timezone-fix-inserted-urgent/07.1-04-SUMMARY.md` — `useUserTimezone()` hook signature, formatting pattern (`{ ...options, timeZone: tz }`)
|
||||
- `.planning/phases/02-mobile-shell-more-drawer/02-CONTEXT.md` — `/mobile` shell layout, sticky header pattern
|
||||
|
||||
### Existing endpoints to reuse (no modification)
|
||||
- `app/api/engagement/user/[userId]/route.ts` — main data source (581 lines): returns `user`, `hours`, `recentEntries`, `recentTeamsMeetings`, `dailyActivity`, `zoom`, `afterHours`, `peerMax`, `snapshots`
|
||||
- `app/api/engagement/user/[userId]/history/route.ts` — monthly history (per-month metrics), **not used in Phase 8 v1**
|
||||
|
||||
### Components to reuse from Phase 7
|
||||
- `components/mobile/EngagementPeriodChips.tsx` — chip component (D-08)
|
||||
- `components/mobile/EngagementUserRow.tsx` exports `getInitials()` (D-05)
|
||||
- `components/mobile/EngagementSummaryCard.tsx` — referenced as visual token source for metric cards
|
||||
- `components/ui/card.tsx`, `components/ui/skeleton.tsx`, `components/ui/collapsible.tsx`, `components/ui/badge.tsx` — shadcn primitives
|
||||
|
||||
### Existing services and helpers
|
||||
- `lib/services/msgraph-factory.ts` — `getMsgraphClient()` for D-25 photo endpoint
|
||||
- `lib/hooks/use-user-timezone.ts` — TZ formatting (D-06, D-18)
|
||||
- `lib/auth-utils.ts` — `requireAuth()` for the photo endpoint
|
||||
|
||||
### Desktop reference (do NOT replicate visuals)
|
||||
- `app/engagement/profile/page.tsx` — desktop modal pattern using recharts (`BarChart`, `RadarChart`); **kept as-is**, not deleted, not migrated. Phase 8 only adds the mobile real-page; desktop modal continues to serve desktop users.
|
||||
|
||||
</canonical_refs>
|
||||
|
||||
<code_context>
|
||||
## Existing Code Insights
|
||||
|
||||
### Reusable Assets
|
||||
- `components/mobile/EngagementPeriodChips.tsx` — drop-in for period selector (D-08)
|
||||
- `components/mobile/EngagementUserRow.tsx` exports `getInitials(displayName)` (D-05)
|
||||
- `lib/hooks/use-user-timezone.ts` — `useUserTimezone()` returns the user's IANA tz string (D-06, D-18)
|
||||
- `lib/services/msgraph-factory.ts` — `getMsgraphClient()` + `isMsgraphConfigured()` for the photo endpoint (D-25)
|
||||
- shadcn `Collapsible` — likely fit for the tap-to-expand recent rows (D-20)
|
||||
|
||||
### Established Patterns
|
||||
- Mobile pages are `'use client'` + `useState` + `useEffect` + `fetch` (CLAUDE.md, Phase 7 D-03)
|
||||
- Sticky chips below H1 use `sticky top-0 z-10 bg-background pt-2 pb-3 -mx-4 px-4` (Phase 7 D-05)
|
||||
- Metric cards: big number `text-2xl font-semibold`, label `text-xs text-muted-foreground`, no shadow, just border (Phase 7 D-10)
|
||||
- TZ-formatted dates use `{ ...options, timeZone: tz }` from `useUserTimezone()` (Phase 7.1)
|
||||
- API routes return JSON; auth via `requireAuth()` from `lib/auth-utils.ts`; error response shape `{ error, message }`
|
||||
|
||||
### Integration Points
|
||||
- Entry: `EngagementUserRow.tsx`'s existing `Link href="/mobile/engagement/{graphUserId}"` (Phase 7 wired this; Phase 8 only creates the destination page)
|
||||
- Data: `/api/engagement/user/[userId]` (existing) + new thin `/api/mobile/engagement/user/[userId]/photo` (D-25)
|
||||
- Auth/middleware: `/api/mobile/*` is whitelisted in `middleware.ts`; route handlers gate via `requireAuth()`
|
||||
- Navigation: Browser back gesture handled by Next.js App Router default scroll restoration (D-04) — no custom code unless verification reveals it's broken
|
||||
|
||||
</code_context>
|
||||
|
||||
<specifics>
|
||||
## Specific Ideas
|
||||
|
||||
- "I want this to feel like the row card I just tapped — same avatar treatment, same identity weight" — header avatar reuses `getInitials()` and the visual rhythm of Phase 7 row cards
|
||||
- "When a manager opens this, they want to see the time numbers first" — 2×2 hero grid leads with Hours/Billable, then Days/Meetings; communication and meeting detail go below in the breakdown card
|
||||
- "Don't bury after-hours" — after-hours% gets a visible row inside Communication subsection rather than being hidden in a tooltip or collapsed section
|
||||
- Tap-to-expand is **inline** (no new page, no modal) — preserves the back-gesture-restores-scroll guarantee from SC#2
|
||||
|
||||
</specifics>
|
||||
|
||||
<deferred>
|
||||
## Deferred Ideas
|
||||
|
||||
- **Peer comparison / radar chart** — desktop has `peerMax` data + a radar visualization; not on mobile v1. Could be a future "compare to team" toggle.
|
||||
- **Monthly history view** — `/api/engagement/user/[userId]/history` exists with 16 metrics × N months. Could power a "history" tab on the profile in a future phase. Not v1.
|
||||
- **`dailyActivity` chart** — the endpoint returns daily breakdown points; could render a single-series sparkline like Phase 7's `EngagementHoursSparkline`. Skipped for v1 to keep the page screen-bounded; reconsider if managers ask for it.
|
||||
- **Tap-to-open ticket/meeting** — D-20 picked tap-to-expand-inline. Future enhancement: an explicit "View ticket" button inside the expanded entry that deep-links to `/mobile/tickets/[id]`.
|
||||
- **D1 ("today") period chip** — Phase 7 D-07 deferred this; same applies here. Aggregate granularity is D7+ until a D1 sync lands.
|
||||
- **Zoom-only client-meeting filter** — desktop differentiates "client meetings" from total meetings using calendar metadata. Mobile v1 shows the totals; client-only breakdown is a future enhancement.
|
||||
|
||||
</deferred>
|
||||
|
||||
---
|
||||
|
||||
*Phase: 08-engagement-user-profile-new*
|
||||
*Context gathered: 2026-05-07*
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
# Phase 8: Engagement User Profile (NEW) - Discussion Log
|
||||
|
||||
> **Audit trail only.** Do not use as input to planning, research, or execution agents.
|
||||
> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered.
|
||||
|
||||
**Date:** 2026-05-07
|
||||
**Phase:** 08-engagement-user-profile-new
|
||||
**Areas discussed:** Identity header, Key metrics layout, Period default, Activity breakdown, Recent items
|
||||
|
||||
---
|
||||
|
||||
## Identity header
|
||||
|
||||
### Avatar source
|
||||
|
||||
| Option | Description | Selected |
|
||||
|--------|-------------|----------|
|
||||
| Initials only (Recommended) | Reuse Phase 7's `getInitials()`. Zero external calls, consistent with overview row. | |
|
||||
| Graph photo with initials fallback | Fetch `user.photo` from Microsoft Graph; nicer visually if photos exist. | ✓ |
|
||||
| Graph photo only (no fallback) | Skip avatar entirely if no photo. | |
|
||||
|
||||
**User's choice:** Graph photo with initials fallback
|
||||
**Notes:** Per D-25/D-26 — new thin endpoint at `/api/mobile/engagement/user/[userId]/photo` calls Graph; client falls back to initials on `<img>` error.
|
||||
|
||||
### Fields under name (multi-select)
|
||||
|
||||
| Option | Description | Selected |
|
||||
|--------|-------------|----------|
|
||||
| Job title | From `graph_users.job_title` — already on the row card | ✓ |
|
||||
| Email | Tap-to-email on phone (`mailto:`) | ✓ |
|
||||
| Department | From `graph_users.department` if populated | ✓ |
|
||||
| Last active timestamp | Most-recent activity (last time entry / last Teams message) | ✓ |
|
||||
|
||||
**User's choice:** All four selected
|
||||
**Notes:** Renders in stacked order: title → department → email → last active. Department/last-active rows hide if NULL.
|
||||
|
||||
---
|
||||
|
||||
## Key metrics layout
|
||||
|
||||
### Layout pattern
|
||||
|
||||
| Option | Description | Selected |
|
||||
|--------|-------------|----------|
|
||||
| 2×2 grid — 4 hero metrics (Recommended) | Mirrors Phase 3 dashboard KPI grid | ✓ |
|
||||
| Stacked cards — 6 metrics | Mirrors Phase 7 overview summary cards | |
|
||||
| Compact strip — 6 in horizontal scroll | Saves vertical, breaks Pulse pattern | |
|
||||
|
||||
**User's choice:** 2×2 grid
|
||||
|
||||
### Default period
|
||||
|
||||
| Option | Description | Selected |
|
||||
|--------|-------------|----------|
|
||||
| D30 — match overview default (Recommended) | Consistent with Phase 7 | ✓ |
|
||||
| D90 — detail-view convention | Heavier data load | |
|
||||
| D7 — most recent context | Spot-check oriented | |
|
||||
|
||||
**User's choice:** D30
|
||||
|
||||
### Top metrics for the 2×2 grid
|
||||
|
||||
| Option | Description | Selected |
|
||||
|--------|-------------|----------|
|
||||
| Hours worked + Billable hours + Days + Meetings | Time-focused set | ✓ |
|
||||
| Hours + Billable + Meetings + After-hours% | Time + workload signal | |
|
||||
| Hours + Meetings + Teams msgs + Emails | Activity-focused | |
|
||||
| Hours + Billable + Days + Meetings + Teams msgs + Emails (6 stacked) | Full picture, stacked | |
|
||||
|
||||
**User's choice:** Hours worked + Billable hours + Days worked + Meetings attended
|
||||
|
||||
---
|
||||
|
||||
## Activity breakdown
|
||||
|
||||
### Breakdown structure
|
||||
|
||||
| Option | Description | Selected |
|
||||
|--------|-------------|----------|
|
||||
| Categorized rows — Time / Communication / Meetings (Recommended) | Three labeled subsections, scannable, no charts | ✓ |
|
||||
| Single mixed list — all metrics flat | Simpler, loses grouping | |
|
||||
| Per-metric mini-sparklines | Visually rich but heavy | |
|
||||
| Daily activity timeline | Combined chart, less per-metric detail | |
|
||||
|
||||
**User's choice:** Categorized rows
|
||||
|
||||
### After-hours% placement
|
||||
|
||||
| Option | Description | Selected |
|
||||
|--------|-------------|----------|
|
||||
| Inside Communication section as a row (Recommended) | Tucked but visible | ✓ |
|
||||
| Standalone row above breakdown | Highlighted callout | |
|
||||
| Drop — not on mobile v1 | Skip for v1 | |
|
||||
|
||||
**User's choice:** Inside Communication section
|
||||
|
||||
---
|
||||
|
||||
## Recent items
|
||||
|
||||
### What gets shown
|
||||
|
||||
| Option | Description | Selected |
|
||||
|--------|-------------|----------|
|
||||
| Recent time entries only (Recommended) | Last ~10 from `recentEntries` | |
|
||||
| Recent meetings only | Last ~10 from `recentTeamsMeetings` | |
|
||||
| Mixed feed — interleaved by date | Single timeline | |
|
||||
| Two separate sections — Time entries + Meetings | Both, kept separate | ✓ |
|
||||
|
||||
**User's choice:** Two separate sections
|
||||
|
||||
### Scope
|
||||
|
||||
| Option | Description | Selected |
|
||||
|--------|-------------|----------|
|
||||
| Last 10 items (Recommended) | Bounded, fits one screen | ✓ |
|
||||
| Last 20 items | Fuller history | |
|
||||
| Last 7 days bounded by date | Time-bounded | |
|
||||
| Match selected period (D7/D30/D90) | Grows with period | |
|
||||
|
||||
**User's choice:** Last 10 items (each section)
|
||||
|
||||
### Tap behavior
|
||||
|
||||
| Option | Description | Selected |
|
||||
|--------|-------------|----------|
|
||||
| Read-only display, no tap action (Recommended) | Match Phase 7's read-only-on-mobile principle | |
|
||||
| Tap to open ticket / meeting detail | Adds nav surfaces | |
|
||||
| Tap to expand inline — show notes, attendees, full details | Stays on profile, reveals more rows | ✓ |
|
||||
|
||||
**User's choice:** Tap to expand inline
|
||||
**Notes:** Local component state (`Set<string>` of expanded IDs); no URL state.
|
||||
|
||||
---
|
||||
|
||||
## Claude's Discretion
|
||||
|
||||
- Exact spacing/typography within Phase 7's established tokens
|
||||
- Whether to use shadcn `Collapsible` or hand-rolled disclosure
|
||||
- Skeleton component composition
|
||||
- Toast wording for 500 errors
|
||||
- Memoization detail of expand-state
|
||||
- "Last active" relative-vs-absolute threshold (started at ≤7d)
|
||||
|
||||
## Deferred Ideas
|
||||
|
||||
- Peer comparison / radar chart (desktop-only flourish)
|
||||
- Monthly history view (`/history` endpoint)
|
||||
- `dailyActivity` chart
|
||||
- Tap-to-open ticket/meeting deep links (future enhancement to inline expand)
|
||||
- D1 "today" period chip (Phase 7 deferral carries forward)
|
||||
- Zoom client-meeting filter (mobile v1 shows totals)
|
||||
Loading…
Add table
Add a link
Reference in a new issue