Customer Portal & My Home — Góc nhìn end-user
A. Khách hàng (Customer Portal)
1. Vào portal: bootstrap theo app mode
app_typequyết định mode host:aoadmin/onsite= Admin Mode; còn lại (Customer Portal, AimyMe =aimyme) = customer mode.- Customer mode: fetch
GET /api/ConsumerAccount(không_a) +GET /api/ConsumerManage/CurrentUser; không gọiGET /api/Manage/CurrentUser/GET /api/Employee/LinkOrg. - Identity và timezone/date-time hydrate từ
ConsumerManage/CurrentUserở mọi mode (INV-USERTZAPI-002).
Ví dụ thực tế: Khách không có staff profile mở portal. → Tránh gọi endpoint staff gây 403. → Bootstrap chỉ gọi
ConsumerAccount+ConsumerManage/CurrentUser; không 403, account hydrate như cũ.
2. Landing: site cards
- Thứ tự row: name → phone (icon phone) → email (icon mail) → address (icon pin); thiếu giá trị → bỏ row đó, các row còn lại giữ thứ tự.
- Icon dùng chung
@ant-design/iconsvới Booking UI (không dùng icon riêng).
Ví dụ thực tế: Site A có phone + email, chưa có address. → Không để row trống. → Card hiện name, phone, email; bỏ row address.
3. Booking UI: Program Details
- Dưới site name (giữ icon pin): row icon mail + site email, rồi row icon phone + site mobile/phone; không có title text.
- Site thiếu email hoặc mobile → không render row tương ứng.
- Click icon info → site info modal cũ giữ nguyên nội dung.
4. Timezone theo phiên
- Single zone (
ServiceAreaId = Local): giờ hiển thị đúng wall-clock source; không convert theo user/browser timezone (mở trên browser khác vẫn cùng giờ). - MultiZone: event time convert về effective timezone từ
User.TimezoneIdentifier; null → fallback browser-detected timezone. - Sidebar quick-select (drawer trái
MenuHamburgerDrawer): Select phía trên Logout, pre-selected theotimezoneIdentifier(null → best-match browser); đổi → auto-save, không nút confirm; disable khi đang save. - Session popup: MultiZone + authenticated +
User.TimezoneIdentifiernull + chưa dismiss → popup tự hiện; save/dismiss đều setsessionStorage['tz-popup-dismissed']. - Guest/anonymous hoặc auth chưa resolve → không bao giờ popup, không check/set session flag.
- Khi
ianaTimezonetrong store khác timezone browser → hiện mismatch modal (chỉ MultiZone, authenticated, chưa dismiss).
Ví dụ thực tế: User mở portal MultiZone, chưa có timezone, chưa từng dismiss. → Chọn đúng múi giờ ngay khi vào. → Popup hiện dropdown; chọn
Australia/Sydney (AEDT)→ save →dayjs.tz.setDefault()đổi, popup không hiện lại trong session.
5. My Subscriptions
- Menu
My Subscriptions→ route/{curl}/enrollments(route quản lý đã có, không tạo mới). - Yêu cầu đăng nhập; chỉ liệt kê enrollment của account đã đăng nhập; client gửi kèm account identifier → backend ignore hoặc reject.
- Row compact → expand: program/session/weekly pattern (7 tag chữ cái Mon–Sun, ngày chọn nổi bật), add-ons, Subscription Summary, Booking Range + View Calendar.
- Chỉ action customer-safe: view details, refresh, cancel, terminate (khi backend policy cho phép); không Confirm, Manage Billing, admin note, account filter.
- Admin-only field bị loại: admin notes, payment method identifier, invoice identifier (trừ khi action invoice yêu cầu), internal booking-order id.
6. My Bookings: subscription section
- Section "My Subscriptions" nằm dưới list term-booking, chỉ khi tab active có status tương ứng:
| Tab | Enrollment status hiển thị |
|---|---|
| Pending | Submitted |
| Confirmed | Approved |
| Cancelled | Canceled |
| Waiting / Cart (Draft) | không render section |
- Card collapsible: header enrollment number + status tag; expand → attendee + subscription name, session block (name, label, time range, weekday tags), Booking Range (period, term label, Booking/Add-on/Total, Invoice Date, View Calendar), add-ons cuối.
- Booked periods hợp lệ:
IsActive = truevàStatusId != Canceled; period cancelled không xuất hiện ở list hay calendar. - Chuyển tab lọc ngay trên data đã fetch; không re-fetch khi đổi tab.
Ví dụ thực tế: Khách mở My Bookings tab Confirmed. → Chỉ xem booking/subscription đã confirm. → Section hiện status
Approved; tab Waiting không hiện; đổi tab Pending lọc ngay, không load lại mạng.
7. Share link
- Nút share (bấm từ Admin Portal hay Customer Portal) luôn tạo URL origin Customer Portal → route booking/program detail của item.
- Giữ param cần thiết (program, term, site, category, TPS); không kèm admin prefix, permission/session artifact (bearer-token query param), route admin-only.
- Link hiển thị trong modal = đúng chuỗi copy vào clipboard.
Ví dụ thực tế: Staff bấm share từ Admin Portal. → Khách phải vào được booking/program, không lộ admin. → Modal hiện URL origin Customer Portal, không token; copy → dán đúng URL đó.
B. Staff (My Home)
My Home header
- Title trái; menu phải render theo BusinessUnit:
| Menu | Điều kiện hiện |
|---|---|
| Calendar | BU có BusinessUnitPermission Staff Roster enabled |
| Timetable | BU có ≥ 1 Class Program Category |
| My Profile | luôn hiện |
- Profile Enterprise-level (không phải Employee/Staff) → menu My Home không hiện (behavior cũ), không gặp timezone prompt.
Ví dụ thực tế: BU chỉ bật Staff Roster, chưa có Class Program Category. → Không hiện menu thừa. → Header hiện title + Calendar + My Profile; Timetable ẩn vì BU chưa có Class category.
Local time (Calendar & Timetable)
ServiceAreaId = Local→ render đúng wall-clock source, không đổi theo timezone user/browser.ServiceAreaId = MultiZone→ convert theoUser.TimezoneIdentifier(null → browser); backend trả giá trị nguồn, không convert theo user.
Ví dụ thực tế: Enterprise MultiZone, staff chưa lưu timezone, mở lịch ở browser
Asia/Ho_Chi_Minh. → Không convert sai giờ khi chưa có cấu hình. →User.TimezoneIdentifiernull → fallback browser timezone; đã lưuW. Australia Standard Time→ convert theo ID đã lưu.
Timezone prompt lần đầu (staff)
- Điều kiện hiện: MultiZone +
User.TimezoneIdentifiernull +sessionStorage['my-home-tz-prompted']falsy. - Modal không dismissible — phải chọn timezone và save; dropdown liệt kê mọi system timezone (IANA + abbreviation).
- Save →
POST /api/User/UpdateUserTimezoneIdentifier(Windows TZ ID) → setmy-home-tz-prompted→ modal đóng, trang render theo timezone mới.
Ví dụ thực tế: Staff mới vào My Home ở enterprise MultiZone, chưa đặt timezone. → Bắt buộc chọn múi giờ để lịch đúng. → Modal hiện dropdown; không đóng được nếu chưa chọn; save xong
my-home-tz-prompted = 1, session này không hỏi lại.
Tóm lại
| Vai | Làm gì |
|---|---|
| Khách hàng | Xem landing site card, Program Details (contact), quản lý My Subscriptions / My Bookings, đặt timezone qua sidebar hay session popup, nhận share link origin portal. |
| Staff | Duyệt My Home (header theo BU), xem Calendar/Timetable theo local time, chọn timezone lần đầu qua prompt bắt buộc. |
| Admin Portal | Tạo share link; gọi bootstrap staff-context; mọi fetch tới Customer Portal vẫn chỉ expose dữ liệu account-scoped, read-only. |