Skip to content

Add Personal Timesheet View to Me Tab on Dashboard - #469

Open
Dharp02 wants to merge 64 commits into
mainfrom
redesign-ui
Open

Add Personal Timesheet View to Me Tab on Dashboard#469
Dharp02 wants to merge 64 commits into
mainfrom
redesign-ui

Conversation

@Dharp02

@Dharp02 Dharp02 commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Overview

This branch implements the full mobile/desktop UI redesign for TimeHuddle — bringing the app to parity with the redesign prototype across layout, navigation, and interaction patterns — plus several quality-of-life fixes and feature additions on top.

Changes by Commit

Navigation & Shell

  • Swap Settings for Huddle in mobile bottom nav — Huddle replaces Settings in the 4-item bottom nav; Settings moves to the sidebar
  • Reorder desktop sidebar Workspace nav — Clock moved up, Work moved to the bottom to match prototype priority
  • Sidebar: embed org/team switcher inline on desktop — then reverted after testing revealed conflicts with the existing popover approach

OrgTeamSwitcher

  • Rebuild OrgTeamSwitcher as prototype-matching bottom-sheet/dialog Modal — full rebuild matching the prototype's trigger button and animated sheet
  • OrgTeamSwitcher: match prototype exactly — refined trigger appearance and transition animation

Dashboard

  • Dashboard: 4-column stat row at desktop widths — stat cards expand to a single 4-col row on wider screens
  • Dashboard: add Me/Team toggle beside the title, scoped stats per tab — Me tab shows your own numbers; Team tab shows the whole team

Clock Page

  • Clock: cap layout to centered max-w-2xl on desktop — content column constrained and centered
  • Clock page: full attach bar, bold session timer, recent sessions — full-width attach bar, bolder session timer, recent sessions shown inline
  • Clock page: widen column, stop dimming the bottom-nav FAB — wider content column; FAB no longer grayed out while clocked in

Huddle

  • Huddle: edge-to-edge feed on mobile — feed goes full width on small screens
  • Huddle: flatten composer chrome, widen feed column — composer chrome reduced; feed column widened on desktop

Tickets

  • Tickets filters: horizontally scrollable chip row on mobile — filter chips scroll horizontally instead of wrapping
  • Tickets: narrow to centered max-w-4xl column on desktop — content column constrained
  • Tickets: fix clipped ticket options menu — overflow clipping on the options dropdown fixed

Teams Page

  • Redesign Teams page: move admin timesheet to Dashboard, add team pill switcher — admin timesheet panel relocated to Dashboard → Team → Timesheet; Teams page gets a pill-style team switcher

Modals & Toasts

  • Mobile bottom-sheet variant for @mieweb/ui Modal — Modal slides up as a sheet on mobile (<768px), centered dialog on desktop
  • Confirm desktop centered-dialog Modal variant preserved — verified modal behavior across breakpoints
  • Toast: shrink-to-content and center at desktop widths — toasts no longer stretch full-width on desktop
  • Fix modals to appear centered instead of full-screen on mobile — corrected modal positioning regression

New Features

  • Add personal timesheet view to Me tab on Dashboard — Overview/Timesheet secondary toggle on the Me tab; loads the current user's own clock history via AdminTimesheetPanel
  • Add share button to team header with compact icon-button strip — share/copy team join link from the team header
  • Add header notification bell and sidebar app info/test push — notification bell in the app header; app version/info in sidebar footer

Docs

  • Add redesign prototype and mobile/desktop implementation plan — prototype HTML and phased implementation checklist added to design/
  • docs(redesign): mark acceptance criteria — tracked completed phases in the plan doc

Acceptance Criteria

  • Mobile bottom nav shows Clock, Huddle, Tickets, Dashboard
  • OrgTeamSwitcher opens as a bottom sheet on mobile, centered modal on desktop
  • Dashboard Me/Team toggle scopes all stat cards correctly
  • Me tab Timesheet view loads current user's clock history
  • Admin timesheet accessible via Dashboard → Team → Timesheet
  • Modals are centered dialogs on desktop, bottom sheets on mobile
  • Toasts are centered and content-width on desktop
  • npm run typecheck and npm run lint pass clean
Screenshot 2026-08-04 at 3 25 18 PM Screenshot 2026-08-04 at 3 25 38 PM Screenshot 2026-08-04 at 3 25 58 PM Screenshot 2026-08-04 at 3 26 19 PM Screenshot 2026-08-04 at 3 26 42 PM Screenshot 2026-08-04 at 3 27 14 PM Screenshot 2026-08-04 at 3 27 29 PM

Dharp02 added 30 commits August 3, 2026 12:09
Step 1 of the redesign plan. Bottom nav tabs are now Dashboard, Huddle,
Clock (center FAB), Tickets, Teams. Settings is still reachable via the
avatar dropdown in the header, matching the prototype's priority order.
Step 2 of the redesign plan. Adds a CSS-only override that turns the
@mieweb/ui Modal into a bottom-sheet on mobile: slides up from the
bottom, full width, rounded top corners only, capped at 88dvh with
internal scroll. Applies to every consumer of the shared Modal (tickets,
teams, org, invite, confirm-delete, feedback, media, etc.) without
forking the component. Desktop behavior is unchanged.
Step 3. Huddle now uses AppPage flush + fill so PostCards stretch to the
viewport edges on mobile (no side gutter, no card radius). Tabs,
composer, drafts, and search input keep px-4 mobile gutter so they still
look inset. Desktop keeps the max-w-4xl reading column (Phase 2 will
tighten this to max-w-2xl). No changes to composer logic or post
rendering.
The quick-stats grid in DashboardPage.tsx already uses
'grid grid-cols-2 gap-3', which satisfies the mobile 2-column requirement
from the redesign plan. Me/Team scope is controlled by the existing
OrgTeamSwitcher / TeamContext, not a separate toggle inside the page, so
no layout change is needed for step 4. Numbers populate correctly for
both personal and team scopes.
Step 5. ClockPage was already a single-column stack (banner status ->
plan/wrap-up composer -> primary action -> punch-clock display). On
mobile the primary action row (Post plan and clock in / Post wrap-up
and clock out / Clock in / Clock out / Break) now stacks vertically
with each Button spanning w-full so the tap target reaches the full
column width. Desktop keeps the horizontal flex-wrap row via
sm:flex-row + sm:w-auto.

Plan-first gate logic in useClockToggle is untouched -- all 11 tests in
useClockToggle.test.ts still pass.
Replaces the anchored Dropdown popover with the shared Modal primitive:
- Mobile: full-width bottom sheet (existing Modal CSS from step 2)
- Desktop: centered dialog (existing Modal CSS from step 10)
- Organization select + Team list with member counts + '+ New team',
  matching design/redesign-prototype/index.html's OrgTeamSwitcher exactly
- Portals the Modal to document.body so it escapes AppHeader's
  backdrop-blur containing block (backdrop-filter creates a new
  containing block for position:fixed descendants, which was pinning
  the sheet near the header instead of the viewport)
- Preserves existing features not in the prototype: org role labels,
  pending join-request badges, awaiting-approval section
- Header trigger now matches the prototype's stacked two-line layout:
  brand mark + bold team name on top, muted 'Org name ▾' below —
  replacing the single-line 'Org > Team' pill.
- Replaced the large translateY(100%) slide-up keyframe with the
  prototype Modal's actual animation: a quiet fade + 4px settle
  (150ms ease-out). The prototype never really 'slides' a large
  distance — it docks the sheet to the bottom and just fades/settles
  it in, which is what read as clean instead of a 'jump'.
- AppPage gains an optional titleActions slot so pages can render
  controls on the same row as the page title (right-aligned), used
  here for the Me/Team segmented toggle from the redesign prototype.
- DashboardPage: 'Me' (default) shows the signed-in user's own hours
  today, open/closed/high-priority tickets, and running timers; 'Team'
  shows the existing team-wide aggregates unchanged.
- The toggle is always visible now (previously personal-team users had
  the team members / time-logged sections silently hidden via
  isPersonalTeam) — switching to Team on a personal team just shows a
  team of one instead of hiding the section.
The prototype's desktop sidebar renders the Organization select + Team
list + '+ New team' inline, always visible — no header button/modal.
Extracted that content into OrgTeamSwitcherPanel (shared with the
mobile bottom sheet) and render it directly in Sidebar.tsx's brand
block for the expanded desktop rail. The header pill + Modal in
OrgTeamSwitcher is now md:hidden (mobile-only) since desktop no longer
needs it — mobile behavior is unchanged.
… switcher

- Teams page: replace tabs with always-visible Members/Pending sections
- Add horizontal-scroll team pill switcher (Personal + selected team lead, rest follow)
- Consolidate Rename/Delete/Copy/Share into Team Settings modal; keep code+copy under team name
- Restore per-member 3-dot menu (Make/Remove Admin, Set Password, Remove Member) for all rows, including self (minus self-demote/remove)
- Compact icon-only Create/Join Team buttons in page title actions
- Move admin Timesheet view from Teams to Dashboard's Team tab with Overview/Timesheet toggle and deep-link support
- Scope the mobile bottom-sheet Modal treatment to only the org/team switcher modal, restoring default centered dialogs everywhere else
- Update e2e tests and notification deep links to match
Give the plan/wrap-up composer the same Photo/Video/Doc/Pulse/Ticket/
@Mention bar as the Huddle composer (extracted into shared
ComposerAttachButtons/ComposerChips so both stay in sync), and match the
redesign prototype's status card (bold elapsed-session timer instead of
the wall clock) and Recent sessions list.
- Override @mieweb/ui's mobile full-screen defaults (min-h-dvh, rounded-none)
  so all modals render as centered popups on every screen size
- Add horizontal margin on small screens so modals don't touch viewport edges
- Move org-switcher bottom-sheet rules outside the 767px media query so the
  bottom-sheet treatment applies on all screen sizes (not just mobile)
Drop the avatar circle and nested flex wrapper from the expanded
composer, trim its padding, and widen the feed's max-width from 2xl to
4xl so it isn't stranded in a narrow strip on wide dashboards.
Replace text "Copy" link with a tight code badge + copy + share icon
button group so the invite code row stays uncluttered. Share uses the
Web Share API with a clipboard fallback for desktop.
Match Clock's column width to Huddle's (max-w-2xl -> max-w-4xl). Also
drop the opacity/saturate dimming on the bottom-nav Clock In/Out FAB
when a plan or wrap-up is pending — it's a plain link to the clock
page, not a disabled control, so it shouldn't look blocked.
The per-row "..." menu used the plain library Dropdown, which
positions its menu absolutely inside the ticket list's
overflow-y-scroll container. Per the CSS overflow spec, once one axis
is scrollable the other axis clips too, so the menu was silently cut
off for rows near the bottom of the list -- the same class of bug
FilterDropdown already works around elsewhere in this file. Portal the
menu to document.body with fixed coordinates computed from the
trigger's rect, same pattern.
- Add meView state ('overview' | 'timesheet') to the Me tab
- Add Overview / Timesheet secondary toggle under Me tab
- Render AdminTimesheetPanel pre-loaded with the current user when meView is 'timesheet'
- Add userToTeamMember helper to convert TimecoreUser to TeamMember shape
Dharp02 added 9 commits August 4, 2026 15:35
The border above the like/comment/share row now stretches to the card's full corners (-mx-5/px-5 instead of -mx-1) instead of stopping short.
Clicking the author avatar or name in the huddle feed now navigates to their profile page.
Meteor.users has no idGeneration override, so its _id is the default Random.id() format (17-char alphanumeric) — not the 24-char hex ObjectId the /app/profile/:id route's regex expected. Raw userIds without a known username (e.g. huddle post authors) were falling through to the username branch and failing lookup ('Unknown user'), including when clicking your own profile from the Huddle feed. Broadened the ID regex to also match Meteor's default 17-char user id format.
Bumped the dashboard's Recent Activity feed cap from 6 to 10 posts; older activity remains reachable via 'View all' on the Huddle page.
Addresses Copilot review comment on PR #469: the .org-switcher-modal overrides (align-self:end, width:100%, top-only rounding) applied unconditionally, forcing a bottom sheet on desktop too. This contradicted OrgTeamSwitcher's own doc comment and the redesign spec (bottom sheet on mobile, centered dialog on desktop). Wrapped the overrides in the same 767px mobile breakpoint used elsewhere in this file.
design/redesign-prototype/index.html was a static mockup used as design reference during the redesign work — not needed in the shipped PR.
- AppHeader: hide the clock-page shortcut on /app/clock so its 'Clock in' aria-label doesn't collide with the ClockPage's own Clock In button (Playwright strict-mode fail).

- ClockPage e2e page object: .clock-banner → .clock-status; status text 'On shift' → 'Clocked in'; plan gate text 'Write a plan before starting this session' → 'Plan before you clock in' — the underlying DOM was renamed during the redesign.

- Team switcher tests (team-qr-share, team-auto-accept, org-invite-by-email, timesheet-calc): getByRole('menuitem', {teamName}) → getByRole('dialog').getByRole('button', {teamName}); the switcher renders a modal of plain buttons now, not a menu.

- team-qr-share: 'Share team QR code' button lives inside Team Settings; the main teams page opens the share modal via 'Share team invite link'.

- teams: 'Copy' button is now 'Copy team code'; disambiguate the created team header by asserting the h3 CardTitle instead of unscoped getByText.

- dashboard: scope sidebar nav-item assertions to the 'Main navigation' nav, since the dashboard body now has its own Timesheet/Team tabs.

- timesheet-calc admin view: scope the 'Timesheet' tab click to <main> to skip the sidebar's identically-named nav button.
- playwright.config: bump retries 1→2 and per-test timeout 30s→45s; grant clipboard-read/write permission so copy-link flows can actually write to the clipboard (previously NotAllowedError).

- loginAs: after waitForURL('**/dashboard') also await waitForLoadState('networkidle') so the post-login session/orgs/teams fanout is settled before the test navigates — otherwise the next page.goto() occasionally races the session-cookie handshake and bounces back to the login screen.

- realtime specs (media-library, messages, notifications, team-members, timesheet): give the login toHaveURL check an explicit 15s timeout — the default 5s occasionally clips when the backend is warming up under sequential load.

- notifications:52, channels:140, pulsevault:348, notifications:28: test.setTimeout(60000) — these are the four historically flaky tests; 45s occasionally isn't enough late in the suite.

- profile-routing selectTestTeam: after page.reload(), wait for networkidle first, then require the Teams heading with a 20s timeout — the reload path is where the auth cookie sometimes hadn't propagated, dropping us on the login screen.

@jlocala1 jlocala1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went through the whole thing and clicked around the preview deploy. The redesign looks good and the commit list in the description made this way easier to follow than a 48 commit PR has any right to be. Couple of things I ran into.

The big one is the notification URLs, left that inline on notify-core.js. Short version is the old links already out there stop working and fail silently. One thing I couldn't check is how many old notifications are actually sitting around, so if those get cleaned up regularly this might be smaller than it looks.

Second thing: there are two personal timesheets now and they don't match. /app/timesheet is still routed and still linked from the sidebar, the More sheet and the command palette. I compared them on the preview with the same account. The old page has a Break Hours card and an Add Entry button. The new Me > Timesheet has neither, and it also shows a Member dropdown where the only option is yourself, since it's reusing AdminTimesheetPanel with a one element member list. So someone who finds the dashboard one first can't log a manual entry and doesn't see their break hours. Which one is meant to be the real one going forward? If it's the dashboard, the panel probably wants a way to hide the member select and the missing pieces ported over.

Also worth knowing, and not your fault: Playwright Checks and Backend Checks are both if: false in checks.yml, so the e2e changes in here and the retries and timeout bumps in playwright.config.ts never actually ran. The green checks are only lint, typecheck and build. Did you get a manual pass over the suite locally?

Two minor ones I didn't bother with inline. The styles.css modal overrides reach into @mieweb/ui internals (data-slot, data-state) with !important across every modal in the app, so a library bump could break it with nothing failing to catch it, might be worth a Modal variant upstream instead. And the More sheet in BottomNav has aria-modal="true" but focus stays on the page behind it when it opens, so keyboard users tab in from the top. If I'm already gone when these are sorted, feel free to dismiss this rather than waiting on me.

date,
userId: actorUserId,
url: `/app/teams?tab=timesheet&memberId=${actorUserId}&teamId=${teamId}`,
url: `/app/dashboard?tab=timesheet&memberId=${actorUserId}&teamId=${teamId}`,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This URL gets saved into the notification document when it's created, it isn't built at render time, so everything already in the database still points at /app/teams?tab=timesheet&memberId=...&teamId=.... I tried that old URL on the preview as an admin and it just drops you on the Teams member roster with no timesheet and no error, it silently strips the query params. Same for any push notification already sitting on someone's phone. Could /app/teams forward tab=timesheet over to the dashboard, or is migrating the existing rows easier? Same line in timers.js.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in both directions. notify-core.js and timers.js now write /app/dashboard?tab=timesheet&... for all new notifications. For existing rows already in the database pointing at /app/teams?tab=timesheet, TeamsPage now detects tab=timesheet in the query string and immediately navigate()s to /app/dashboard?tab=timesheet&memberId=...&teamId=... before stripping the params — so those old links land on the right view instead of dropping silently.

setTeamView('timesheet');
}
if (memberId) setInitialMemberId(memberId);
if (teamId && teams.some((t) => t.id === teamId)) setSelectedTeamId(teamId);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question more than anything since I couldn't test it. teams from useTeam() is scopedTeams, filtered by selectedOrgId. So if the notification is for a team in a different org than whatever's currently selected, the match fails, canViewTimesheet stays false, the fallback effect resets teamView to overview, and replaceState has already wiped the params so there's no second chance. The comment in timesheet-calculation-fixes.spec.ts sounds like the same thing and works around it by switching teams by hand. I couldn't set up a second org on the preview to check. Have you run into that?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Fixed by exposing allTeams (the unscoped array, before org filtering) from TeamContext alongside the existing scoped teams. The deep-link effect now checks allTeams when the teamId isn't found in the current org's teams. If the team is found there, it calls setSelectedOrgId(team.orgId) first, then setSelectedTeamId(teamId), so the org switch happens in the same React batch and the auto-correction effect sees the team as in-scope.

{/* ── Me Timesheet view ────────────────────────────────────────────── */}
{tab === 'me' && meView === 'timesheet' && user && selectedTeamId && (
<AdminTimesheetPanel
members={[userToTeamMember(user)]}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small one. This builds a new array every render, and TeamContext runs setInterval(..., 1000) on currentTime, so the dashboard re-renders once a second and the panel's auto-select effect and memberOptions memo re-run every tick. A useMemo keyed on user?.id would settle it.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer applicable. The Me → Timesheet view now renders with no props rather than <AdminTimesheetPanel members={[userToTeamMember(user)]} ...>, so the array isn't built at all on the Me tab. The memberOptions memo in AdminTimesheetPanel only runs when it's shown on the Team tab, where it receives the full pre-fetched teamMembers list.

Dharp02 added 15 commits August 5, 2026 19:30
Removes the decorative ClockStrand from the desktop header middle section. The header now shows only the timer pill and action buttons when clocked in.
…e ambient background CSS

Restores the original translateX-only keyframe. Removes all ambient-bg orb rules that were added for the background color experiment.
Removes the app-bg class and initAmbientBg hook call from AppLayout. Removes AmbientBgRow toggle from Settings Appearance section and deletes useAmbientBg.ts.
Removes the ClockStrand from the timer pill so mobile shows only the HH:MM:SS digits. Also removes the now-unused ClockStrand import.
Reduces gap, card padding, and timer font size on small screens. Hides the decorative wave strand on mobile. Shrinks the composer heading on mobile. All layout values restore to their original sizes at md breakpoint.
After updateSettings saves requirePlanForClock: true, fan-out a notification to every team member (except the admin who made the change) via createNotification, which persists to the inbox and fires a push.
Initialises teamFilter from selectedTeamId so the list opens pre-filtered to the active team. A useEffect also follows the team switcher so changing teams in the header updates the filter automatically.
…BottomNav focus trap, styles.css comment

- TeamsPage: forward old /app/teams?tab=timesheet notification URLs to /app/dashboard
- TeamContext: expose allTeams (unscoped) alongside scoped teams
- DashboardPage: switch org when deep-link teamId belongs to a different org
- BottomNav More sheet: move focus in on open, trap Tab/Shift+Tab, restore focus to trigger on close
- styles.css: document data-slot/data-state internals dependency with TODO to fix upstream
…k active tickets

- Remove Me/Team scoping from the Dashboard entirely
- Single Overview/Timesheet toggle in the content area (no more two-toggle confusion)
- Timesheet tab shows AdminTimesheetPanel for admins, PersonalTimesheetPanel for others
- Stats cards always show team data
- Active ticket rows are now clickable buttons that navigate to /app/tickets/:id
…ew/Timesheet

- Me/Team pill toggle back in the title bar
- Stats cards (Hours, Open tickets, Closed today, High priority) scope to Me or Team
- Team Members and Time logged today sections only appear on Team tab
- Single Overview/Timesheet toggle in content (no duplicate toggles)
- Active ticket rows remain clickable, navigating to /app/tickets/:id
@Dharp02
Dharp02 requested a review from jlocala1 August 6, 2026 01:55
@horner

horner commented Aug 6, 2026

Copy link
Copy Markdown
Member

🚀 Preview Deployment Ready

Service URL
App https://mieweb-timehuddle-redesign-ui.os.mieweb.org
API (Meteor) https://mieweb-timehuddle-redesign-ui-api.os.mieweb.org

Preview auto-deletes when this PR is closed.

Oh. I see it. Are there not test roles for auto logging in?

Every test environment should have a drop-down role that we can just login automatically with a single click

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

4 participants