← 回看板
draft

管理員端顧客卡券管理:搜尋/篩選卡券清單+詳情 Drawer+強制刪除撤銷(必填異動原因)

manage-customer-vouchers · 建立於 2026-05-17
🎨 設計稿:M+ Admin 原型 · 顧客卡券頁 ↗

prospec 流程進度

探索需求規劃設計任務實作驗證歸檔

跨 repo 進度

owl(產品層)
定意圖
2026-08-22 套用 Jacky 決議:刪除改為管理頁面 UI 操作(002 規格部分同步:FR 與介面描述已改,驗收場景與 revoked_at 模型待修)、「其他」開放說明欄。使用進度呈現方式待決
cockatiel(後端)
待開始
void/revoke 為既有 API 可重用;異動原因與 IP 欄位需擴充,本 change 專屬工作未開始
raven(前端)
待開始
無 /admin/* 路由,管理員端頁面與 Drawer 元件尚未存在

manage-customer-vouchers

Epic 1:顧客卡券管理 (Customer Voucher Management) — 對應頁面:顧客卡券 設計稿依據:「M+ Admin.html」(2026-08-06) 顧客卡券頁,2026-08-10 依團隊提供的完整 User Story 文件(含詳情面板、上方數字、異動紀錄的網路位置欄位)第二次改版,取代 08-09 的推測版本。

Background

顧客打來問卡券還剩幾次、要退費、或是開錯卡要取消,管理員現在沒有一個地方可以查、也沒辦法自己刪,只能請工程幫忙,而且刪掉之後沒有留下誰刪的、為什麼刪。

本變更新增「顧客卡券」管理頁面,讓管理員自己查得到、也刪得掉,並且每次刪除都留下紀錄。對應 10/3 第一階段上線需求「報表系統 › 顧客卡券(可刪除)」。

User Stories

Edge Cases

  • 已失效/已用完的卡券:還是查得到,清單與詳情面板都會顯示,並標示狀態
  • 重複刪除:已經作廢的卡券再點一次刪除,系統要擋下來並說明原因
  • 空白搜尋:什麼都沒輸入時顯示全部(分頁呈現),不能報錯
  • 同名顧客:有多位顧客同名時,清單要能用手機或頭像分辨出來
  • 序號與手機的輸入格式:不管有沒有加 MP- 前綴、手機有沒有打分隔線,都要搜得到

Functional Requirements

  • FR-001: 提供卡券搜尋,支援顧客姓名、手機(含/不含 -)、卡券序號(MP-…)三種查詢條件
  • FR-002: 提供「六大類別」與「狀態」兩組篩選標籤,可以同時選。狀態值為使用中/已用畢/已退費三種(2026-08-23 依設計稿確定)
  • FR-003: 卡券清單顯示顧客頭像/姓名/手機、卡券序號、名稱、類別、剩餘次數(含進度條)、剩餘金額、狀態;標題列顯示「共 N 張卡券 · 已篩選 M 筆」的文字計數(2026-08-24 依設計稿修訂,原為 KPI 卡)
  • FR-004: 點卡券任一列會開啟詳情面板,內容有剩餘次數/金額、購買金額、單次價值、開卡日期、開卡人員、使用進度、可用狀態、完整異動時間軸
  • FR-004a(2026-08-23 決議): 「使用進度」對門市開的卡與匯入的卡一律顯示進度條,分母是「這張卡開出/匯入進來時共有幾次」;匯入前的使用歷程不算在內,畫面需說明這一點
  • FR-005: 清單上和詳情面板裡都要有刪除/撤銷的入口
  • FR-006: 刪除是「作廢」而不是真的清掉——顧客端與管理端都看不到,但資料留在系統裡可追查
  • FR-007: 刪除前要跳確認視窗,而且一定要選原因(顧客退費/輸入錯誤撤銷/店家補償重發/其他)
  • FR-007a: 原因選「其他」時要出現文字欄位讓管理員補充(是否必填、字數上限見 Open Questions)
  • FR-007b: 刪除/撤銷是管理員在後台頁面上就能完成的操作,不需要工程協助;權限僅限管理員
  • FR-008: 每次刪除都要留下可追查的紀錄(誰刪的、時間、原因、從哪個網路位置),並顯示在該卡券的詳情面板時間軸上
  • FR-009: 實際的退費金流不在這次範圍內
  • FR-010(2026-08-23 依設計稿補): 提供「匯出名單」,匯出目前篩選結果的卡券清單

Success Criteria

  • SC-001: 用姓名、手機、序號三種方式都查得到正確的卡券清單,篩選標籤可以疊加使用,標題列計數隨篩選同步更新
  • SC-002: 刪掉的卡券 100% 不再出現在顧客那邊,也 100% 留在系統裡查得到(含時間軸事件)
  • SC-003: 每一次刪除都產生含誰刪的/時間/原因/網路位置的紀錄,完整率 100%

Related Modules

  • (owl 為產品層 spec hub,無程式模組)
  • 後端:已具備作廢卡券的能力,但目前不會記錄「為什麼刪」與「誰在哪裡刪的」,需要補上
  • 前端:管理後台目前沒有這個頁面,要新做(含右側滑出的詳情面板)
  • 技術細節(資料表、欄位、既有功能的差異)見同目錄 implementation-notes.md

Open Questions

2026-08-22 由 Jacky 逐題答覆(Ci 轉述)。決議彙整見 docs/grooming/2026-08-22-admin-console-open-questions-resolved.md

  • 跨規格衝突:本 US-2 定義為「管理後台 UI 操作」,specs/002-voucher-ui-enhancement 定義為「API only、僅 ADMIN」2026-08-22 決議:以本 change 為準,管理員於管理頁面可刪除卡券。所有管理員皆可操作(manage-account-permissions 已確立無管理員內部分層)
    • ⚠️ 待辦:舊版規格(002)還寫著「只能透過程式介面操作」,需同步改掉。細節見 implementation-notes.md
  • 異動原因「其他」是否需要開放文字說明欄位2026-08-22 決議:需要(見 FR-007a)
  • NEEDS CLARIFICATION(2026-08-22 細化): 「其他」的文字說明欄位是否必填?字數上限?
  • 詳情面板「使用進度」的計算基礎(次數型 vs 金額型呈現是否不同)2026-08-23 決議:兩種卡券都顯示進度條,分母為「綁定/開卡當下的總次數」(即綁定後進度,非終身進度)
    • 已查證現有系統做得到,不需要改資料庫
    • 已知限制(需在畫面上說明):舊系統在匯入之前已經用掉的次數查不到,所以進度的分母是「匯入進來時還剩幾次」。例:原本 10 次、匯入前已用 3 次、只匯入剩下的 7 次 → 畫面顯示「0/7」而非「3/10」
    • 技術依據見 implementation-notes.md
  • 贈品與產品的業務差異 — 已由卡券方案維護(Epic 4)文件釐清,與本變更無關
  • 5 月產出的設計規格是照舊版畫面寫的,要對齊這次的詳情面板與時間軸重做

Constitution Check

  • Reviewed against prospec/CONSTITUTION.md(2026-05-24 版)
  • P1(owl 是產品層):PASS — 僅描述意圖與行為,詳情面板/時間軸為行為描述非技術實作
  • P2(契約意圖在 owl、實體在 cockatiel):PASS — 未指定 endpoint/schema;void/revoke 實體以 cockatiel openapi.yaml 為權威
  • P3/P4(兩軌分離、協調點):PASS — 無新增手動同步步驟

UI Scope

Scope: full

(設計稿已存在:M+ Admin 顧客卡券頁,要重新萃取才會包含詳情面板的互動細節。)

Design Spec: Manage Customer Vouchers

Generated from: proposal.md (ui_scope: full) Platform: figma Last updated: 2026-05-17

Overview

An admin (management-side) page that lets branch administrators search a customer's vouchers and lets high-privilege roles revoke (void) a voucher with a confirmation step. Covers proposal stories US-1 (search) and US-2 (revoke).

This design reuses the existing M+ hair-salon design system — the shared "Design Tokens" variable collection (warm gold/cream identity) already used by the M+ Customer and Designer portals — so the admin surface is visually consistent with the rest of the product.


Visual Identity

Color Palette

Token Value Usage
--color-primary #ca8a04 Primary actions, active states, links
--color-primary-hover #a16207 Primary hover
--color-primary-muted #ca8a0415 Subtle tinted backgrounds, pills
--color-primary-border #ca8a0433 Card / input borders
--color-surface #ffffffe6 Card backgrounds
--color-surface-solid #ffffff Inputs, table surface
--color-surface-muted #fafaf9 Table header / zebra rows
--color-background #f5f5f4 Page background
--color-text #0c0a09 Primary text
--color-text-secondary #44403c Secondary text
--color-text-muted #78716c Labels, captions
--color-text-disabled #a8a29e Disabled / revoked rows
--color-border #e7e5e4 Table dividers
--color-success #16a34a / bg #dcfce7 Status: 有效 (active)
--color-error #dc2626 / bg #fee2e2 Destructive action, status: 已撤銷
--color-disabled-bg #f3f4f6 Status: 已失效 / 已用完

Typography

Family: Noto Sans TC (CJK-capable). Latin/numeric also rendered with Noto Sans TC.

Token Size Weight Usage
--font-heading-lg 28px 700 Page title
--font-heading 20px 700 Card / dialog titles
--font-title 16px 500 Table column headers, row primary text
--font-body 14px 400 Table cell text, buttons
--font-small 13px 400 Secondary cell text
--font-caption 12px 400 Captions, helper text

Spacing Scale

Token Value Usage
--space-xs 4px Inline gaps
--space-sm 8px Compact padding, pill padding
--space-md 16px Default padding, cell padding
--space-lg 24px Card padding, section spacing

Visual Style

  • Border radius: 22px (cards/dialog), 12px (inputs/buttons-rect), 999px (pill badges)
  • Shadow: 0 25px 50px #ca8a042e (card), 0 16px 40px #00000026 (dialog)
  • Transition: 150ms ease-in-out
  • Page background: warm radial gradient (#fff8e1 → #fafaf9 → #f8f5f0)

Components

TopBar

Layout: flex, horizontal, space-between, height 64px, full width, padding 0 32px States: single static state. Design Tokens: background --color-surface, bottom border --color-primary-border. Children:

  • Brand: M+ logo mark + "M+ 管理後台" label
  • Admin meta: admin display name + role badge + logout button

PageHeader

Layout: flex, vertical, gap 8px Children:

  • Title "顧客卡券管理" (--font-heading-lg, --color-text)
  • Description "搜尋並管理顧客卡券,協助確認餘額或撤銷錯誤卡券。" (--font-body, --color-text-muted)

SearchBar

Layout: card, flex horizontal, gap 12px, align center, padding --space-lg States:

State Visual Changes
Default Empty input, placeholder visible
Focused Input border --color-primary
Disabled (search btn) Empty input → search button --color-disabled-bg

Children:

  • Search input: leading search icon + text field, placeholder "輸入顧客姓名、手機號碼或卡券序號"
  • Search button: primary, label "搜尋"
  • Helper text (empty submit): "請輸入搜尋條件" (--color-error)

VoucherTable

Layout: card; vertical; header row + data rows; columns separated by --space-md Columns: 顧客 | 卡券類型 | 剩餘 | 購買日期 | 失效日期 | 狀態 | 操作

States:

State Visual Changes
Default row Surface background, --color-text text
Revoked row --color-text-disabled text, no revoke action
Hover row --color-surface-muted background

Design Tokens: header background --color-surface-muted, row divider --color-border. Children: rows of [CustomerCell, TypeCell, RemainingCell, DateCell ×2, StatusBadge, RevokeButton]

StatusBadge

Layout: pill, padding --space-sm --space-xs, radius 999px States (variants):

Variant Background Text Meaning
active --color-success-bg --color-success 有效
used-up --color-disabled-bg --color-text-disabled 已用完
expired --color-disabled-bg --color-text-disabled 已失效
revoked --color-error-bg --color-error 已撤銷

RevokeButton

Layout: text/ghost button, --font-body States:

State Visual Changes
Default (high-priv) --color-error label "撤銷"
Hover underline
Hidden (normal admin) Not rendered — only high-privilege roles see it
Disabled (already revoked) Not rendered for revoked rows

EmptyState

Layout: flex vertical, center aligned, gap --space-md, padding 48px Children: package icon (64px circle, --color-surface-muted), title "查無卡券", description "找不到符合搜尋條件的卡券,請確認姓名、手機或序號是否正確。"

RevokeConfirmDialog

Layout: modal — full-screen overlay (--color-overlay) + centered dialog card (radius 22px, dialog shadow, max-width 440px, padding --space-lg) States:

State Visual Changes
Visible Overlay + dialog shown
Submitting Confirm button spinner, both buttons disabled
Already-revoked block Replaces actions with notice "此卡券已撤銷"

Children:

  • Title "撤銷卡券確認"
  • Voucher summary block: 顧客 / 卡券類型 / 卡券序號 / 剩餘 / 購買日期
  • Warning text "撤銷後此卡券將於顧客端隱藏且無法復原,系統會記錄本次操作。"
  • Actions: 取消 (secondary) + 確認撤銷 (destructive, --color-error fill)

Responsive Strategy

This is a desktop-first admin tool. Primary target is desktop (≥1024px).

Breakpoints

Name Min Width Layout
Tablet 768px Content full-width with 24px gutter; table scrolls horizontally
Desktop 1024px Centered content container, max-width 1200px

Layout Adaptations

Component Tablet Desktop
SearchBar Input + button stack-friendly, still inline Inline, input grows
VoucherTable Horizontal scroll inside card All columns visible
RevokeConfirmDialog 90% viewport width Fixed 440px width

Navigation

  • Desktop: persistent top bar; admin sections reachable from top bar (out of scope here).
  • The page itself is a single-view destination — no in-page navigation.

Interaction Spec: Manage Customer Vouchers

Generated from: proposal.md + design-spec.md DSL Version: draft-1 Last updated: 2026-05-17

Screens

Screen: VoucherManagement

States:

State Description Entry Condition
Idle Search bar shown, no query run yet Initial visit
InvalidSubmit Empty search submitted Search clicked with blank input
Loading Fetching voucher results Valid search submitted
Results Voucher table rendered with rows Fetch success, ≥1 match
NoResults Empty state shown Fetch success, 0 matches
Error Fetch failed notice Fetch error

Transitions:

Idle          -> InvalidSubmit : search click with empty input
InvalidSubmit -> Loading       : search click with non-empty input
Idle          -> Loading       : search click with non-empty input
Loading       -> Results       : fetch success (matches > 0)
Loading       -> NoResults     : fetch success (matches == 0)
Loading       -> Error         : fetch error
Error         -> Loading       : retry click
Results       -> Loading       : new search submitted

Branch scoping (FR-003) is applied server-side: results only ever contain vouchers of the administrator's own branch. Cross-branch matches never enter the Results state.

Component: RevokeConfirmDialog

States:

State Description Entry Condition
Hidden Not shown Default
Visible Overlay + dialog with voucher summary Revoke clicked on an active row
Submitting Confirm pressed, awaiting result Confirm click
AlreadyRevoked Notice "此卡券已撤銷", revoke blocked Revoke attempted on an already-revoked voucher

Transitions:

Hidden     -> Visible        : revoke click (row not revoked)
Hidden     -> AlreadyRevoked : revoke click (row already revoked)
Visible    -> Submitting     : confirm revoke click
Visible    -> Hidden         : cancel click / overlay click
Submitting -> Hidden         : revoke success (row updates to 已撤銷)
Submitting -> Visible        : revoke error (inline error, re-enable)
AlreadyRevoked -> Hidden     : dismiss click

Flows

Flow: Search a customer's vouchers (US-1)

Description: An administrator looks up a customer's vouchers to confirm balance.

Steps:

1. User types a name / phone / voucher serial into the search input
   -> Search button becomes enabled

2. User clicks 搜尋
   -> If input empty: stay on screen, show helper "請輸入搜尋條件"
   -> If valid: show Loading state

3. System fetches branch-scoped voucher results
   -> On success with matches: render VoucherTable (customer, type,
      remaining, purchase date, expiry date, status)
   -> On success with no match: render EmptyState "查無卡券"
   -> On error: show Error notice with retry

Flow: Revoke a voucher (US-2)

Description: A high-privilege administrator voids an incorrect voucher.

Steps:

1. (Precondition) Revoke action is visible only to high-privilege roles
   (store manager / HQ admin). Normal admins never see the 撤銷 button.

2. User clicks 撤銷 on an active voucher row
   -> Open RevokeConfirmDialog (Visible) showing the voucher summary
   -> If the voucher is already revoked: open AlreadyRevoked state instead

3. User reviews voucher info + warning text
   -> Click 取消: close dialog, voucher unchanged
   -> Click 確認撤銷: dialog enters Submitting (confirm spinner)

4. System soft-deletes the voucher and writes an audit log
   (operator, timestamp, voucher identity, prior status)
   -> On success: close dialog, row status updates to 已撤銷 (greyed, no action),
      voucher is hidden on the customer side
   -> On error: stay in dialog, show inline error, re-enable buttons

Gestures & Micro-interactions

Trigger Animation Duration
Button press Background color shift to hover/active 150ms
Dialog appear Overlay fade in + dialog scale 0.96 → 1.0 150ms
Row status change to 已撤銷 Row text fades to disabled color 200ms
Search submit Search button shows inline spinner

Responsive Interactions

Interaction Tablet Desktop
Browse table columns Horizontal scroll within table card All columns visible at once
Revoke action Tap 撤銷 → dialog at 90% width Click 撤銷 → fixed 440px dialog
Search Inline input + button Inline input + button