diff --git a/docs/superpowers/plans/2026-07-01-stage-1-7-ticket-experience-visual-redesign.md b/docs/superpowers/plans/2026-07-01-stage-1-7-ticket-experience-visual-redesign.md index 4c16454..f4f5a0b 100644 --- a/docs/superpowers/plans/2026-07-01-stage-1-7-ticket-experience-visual-redesign.md +++ b/docs/superpowers/plans/2026-07-01-stage-1-7-ticket-experience-visual-redesign.md @@ -45,7 +45,7 @@ Frontend: - Modify: `backend/apps/tickets/services.py` - Modify: `backend/apps/tickets/tests/test_ticket_api.py` -- [ ] **Step 1: Write failing pagination/filter/display tests** +- [x] **Step 1: Write failing pagination/filter/display tests** Add these tests to `backend/apps/tickets/tests/test_ticket_api.py`: @@ -125,7 +125,7 @@ def test_transition_ticket_requires_non_empty_remark(admin_client, db): assert response.status_code == 400 ``` -- [ ] **Step 2: Run tests to verify RED** +- [x] **Step 2: Run tests to verify RED** Run: @@ -136,7 +136,7 @@ python -m pytest apps/tickets/tests/test_ticket_api.py -v Expected: fails because list response is currently a list, display users are missing, advanced filters are missing, and empty transition remark is allowed. -- [ ] **Step 3: Implement schemas and serialization** +- [x] **Step 3: Implement schemas and serialization** Add schemas to `backend/apps/tickets/schemas.py`: @@ -196,7 +196,7 @@ def serialize_ticket(ticket: Ticket) -> dict: } ``` -- [ ] **Step 4: Implement paginated list and filters** +- [x] **Step 4: Implement paginated list and filters** Change `list_tickets` to return `TicketListOut` and accept: @@ -229,7 +229,7 @@ page = max(page, 1) page_size = min(max(page_size, 1), 100) ``` -- [ ] **Step 5: Require transition remarks** +- [x] **Step 5: Require transition remarks** In `update_ticket_status`, reject blank remarks: @@ -240,7 +240,7 @@ if not payload.remark.strip(): Import `HttpError` from `ninja.errors`. -- [ ] **Step 6: Update existing tests for new list shape** +- [x] **Step 6: Update existing tests for new list shape** Change existing list assertions in `test_list_tickets_filters_by_status`: @@ -250,7 +250,7 @@ assert payload["total"] == 1 assert payload["items"][0]["ticket_no"] == "TK202606300001" ``` -- [ ] **Step 7: Verify GREEN** +- [x] **Step 7: Verify GREEN** Run: @@ -262,7 +262,7 @@ python -m ruff check . Expected: all selected tests pass and ruff has no errors. -- [ ] **Step 8: Commit** +- [x] **Step 8: Commit** ```bash git add backend/apps/tickets @@ -278,7 +278,7 @@ git commit -m "feat: add ticket list visual support api" - Create: `frontend/src/features/tickets/TicketMetricCards.tsx` - Create: `frontend/src/features/tickets/TicketFilterBar.tsx` -- [ ] **Step 1: Refresh shadcn docs** +- [x] **Step 1: Refresh shadcn docs** Run: @@ -289,7 +289,7 @@ npx shadcn@latest docs tabs table card badge select input pagination skeleton so Use installed components first. Do not overwrite existing locally patched components. -- [ ] **Step 2: Update ticket frontend types** +- [x] **Step 2: Update ticket frontend types** In `frontend/src/features/tickets/types.ts`, add: @@ -332,7 +332,7 @@ export type TicketListParams = { } ``` -- [ ] **Step 3: Update ticket API** +- [x] **Step 3: Update ticket API** In `frontend/src/features/tickets/api.ts`, update `listTickets` to return `TicketListResponse` and include all query fields: @@ -349,7 +349,7 @@ export function listTickets(params: TicketListParams = {}) { } ``` -- [ ] **Step 4: Create TicketMetricCards** +- [x] **Step 4: Create TicketMetricCards** Create `frontend/src/features/tickets/TicketMetricCards.tsx` with compact shadcn `Card` metrics for: @@ -368,7 +368,7 @@ type TicketMetricCardsProps = { } ``` -- [ ] **Step 5: Create TicketFilterBar** +- [x] **Step 5: Create TicketFilterBar** Create `frontend/src/features/tickets/TicketFilterBar.tsx` using `AppFilterBar`, `Input`, `Select`, and `Button`. @@ -388,7 +388,7 @@ type TicketFilterBarProps = { } ``` -- [ ] **Step 6: Redesign TicketListPage** +- [x] **Step 6: Redesign TicketListPage** Update `TicketListPage`: @@ -400,7 +400,7 @@ Update `TicketListPage`: - Table columns must be Chinese and include requester/assignee display when available. - Empty/error/loading states must be Chinese. -- [ ] **Step 7: Verify frontend GREEN** +- [x] **Step 7: Verify frontend GREEN** Run: @@ -412,7 +412,7 @@ npm run build Expected: both pass. -- [ ] **Step 8: Commit** +- [x] **Step 8: Commit** ```bash git add frontend/src/features/tickets @@ -428,7 +428,7 @@ git commit -m "feat: redesign ticket list experience" - Create: `frontend/src/features/tickets/TicketTransitionDialog.tsx` - Create: `frontend/src/features/tickets/TicketMetaPanel.tsx` -- [ ] **Step 1: Refresh shadcn docs** +- [x] **Step 1: Refresh shadcn docs** Run: @@ -437,7 +437,7 @@ cd frontend npx shadcn@latest docs dialog textarea card badge separator tabs sonner ``` -- [ ] **Step 2: Create TicketTransitionDialog** +- [x] **Step 2: Create TicketTransitionDialog** Create a dialog component with: @@ -461,7 +461,7 @@ Visible text: The submit button is disabled until `remark.trim()` is non-empty. -- [ ] **Step 3: Create TicketMetaPanel** +- [x] **Step 3: Create TicketMetaPanel** Create `TicketMetaPanel` with shadcn `Card`, `Badge`, `Separator`, `AppField`, `AppStatusBadge`, and `AppPriorityBadge`. @@ -477,7 +477,7 @@ Display: 影响范围 ``` -- [ ] **Step 4: Update TicketStatusTimeline** +- [x] **Step 4: Update TicketStatusTimeline** Make every event label Chinese: @@ -491,7 +491,7 @@ const EVENT_LABELS: Record = { } ``` -- [ ] **Step 5: Update TicketComments** +- [x] **Step 5: Update TicketComments** Use Chinese labels and distinguish: @@ -506,7 +506,7 @@ Use shadcn `Tabs` or `Select` to choose visibility. Existing API can still recei { comment_type: 'internal_note' | 'user_reply', visibility: 'internal' | 'public' } ``` -- [ ] **Step 6: Redesign TicketDetailPage** +- [x] **Step 6: Redesign TicketDetailPage** Update page: @@ -516,7 +516,7 @@ Update page: - Main column contains `问题描述`, `处理记录`, `评论与协作`. - Side column uses `TicketMetaPanel`, `SolutionLinkPanel`, and restrained AI placeholder. -- [ ] **Step 7: Verify frontend GREEN** +- [x] **Step 7: Verify frontend GREEN** Run: @@ -528,7 +528,7 @@ npm run build Expected: both pass. -- [ ] **Step 8: Commit** +- [x] **Step 8: Commit** ```bash git add frontend/src/features/tickets @@ -540,7 +540,7 @@ git commit -m "feat: redesign ticket detail experience" **Files:** - Modify: `frontend/src/features/tickets/TicketCreatePage.tsx` -- [ ] **Step 1: Refresh shadcn docs** +- [x] **Step 1: Refresh shadcn docs** Run: @@ -549,7 +549,7 @@ cd frontend npx shadcn@latest docs card input select textarea badge alert ``` -- [ ] **Step 2: Redesign create page** +- [x] **Step 2: Redesign create page** Update `TicketCreatePage` into grouped sections: @@ -563,7 +563,7 @@ Update `TicketCreatePage` into grouped sections: Visible copy must be Chinese. Use `Card`, `Input`, `Select`, `Textarea`, `Badge`, and `Button`. -- [ ] **Step 3: Use Chinese error feedback** +- [x] **Step 3: Use Chinese error feedback** On create failure, show: @@ -571,7 +571,7 @@ On create failure, show: 创建工单失败,请检查必填信息或稍后重试。 ``` -- [ ] **Step 4: Run full verification** +- [x] **Step 4: Run full verification** Run: @@ -588,7 +588,7 @@ npx shadcn@latest info Expected: all commands pass. -- [ ] **Step 5: Browser visual smoke test** +- [x] **Step 5: Browser visual smoke test** With local backend/frontend running: @@ -597,7 +597,7 @@ With local backend/frontend running: - Open a ticket detail page and verify transition dialog requires handling note. - Open `#/tickets/new` and verify grouped create form has no obvious overlap. -- [ ] **Step 6: Commit** +- [x] **Step 6: Commit** ```bash git add frontend/src/features/tickets @@ -606,15 +606,15 @@ git commit -m "feat: redesign ticket create experience" ## Final Review Checklist -- [ ] Ticket list uses shadcn tabs, cards, table, filters, and pagination. -- [ ] Ticket detail uses a Chinese transition dialog and no English loading/error copy. -- [ ] Ticket comments distinguish internal notes and user-visible replies. -- [ ] Ticket create page uses grouped Chinese form sections. -- [ ] Backend ticket list supports pagination and visual filters. -- [ ] Ticket responses include requester and assignee display fields. -- [ ] Empty transition remarks are rejected. -- [ ] Backend ticket tests pass. -- [ ] Backend ruff passes. -- [ ] Frontend typecheck passes. -- [ ] Frontend build passes. -- [ ] shadcn info still reports Vite/Tailwind v3 and installed components. +- [x] Ticket list uses shadcn tabs, cards, table, filters, and pagination. +- [x] Ticket detail uses a Chinese transition dialog and no English loading/error copy. +- [x] Ticket comments distinguish internal notes and user-visible replies. +- [x] Ticket create page uses grouped Chinese form sections. +- [x] Backend ticket list supports pagination and visual filters. +- [x] Ticket responses include requester and assignee display fields. +- [x] Empty transition remarks are rejected. +- [x] Backend ticket tests pass. +- [x] Backend ruff passes. +- [x] Frontend typecheck passes. +- [x] Frontend build passes. +- [x] shadcn info still reports Vite/Tailwind v3 and installed components. diff --git a/frontend/src/components/app/AppDataTable.tsx b/frontend/src/components/app/AppDataTable.tsx index 9d28a9f..05501f4 100644 --- a/frontend/src/components/app/AppDataTable.tsx +++ b/frontend/src/components/app/AppDataTable.tsx @@ -19,6 +19,7 @@ type AppDataTableProps = { emptyDescription?: string toolbar?: React.ReactNode footer?: React.ReactNode + tableClassName?: string onRowClick?: (row: Row) => void } @@ -30,6 +31,7 @@ export function AppDataTable({ emptyDescription, toolbar, footer, + tableClassName, onRowClick }: AppDataTableProps) { return ( @@ -41,7 +43,7 @@ export function AppDataTable({ ) : (
- +
{columns.map((column) => ( diff --git a/frontend/src/features/tickets/TicketCreatePage.tsx b/frontend/src/features/tickets/TicketCreatePage.tsx index 235f392..778c9fc 100644 --- a/frontend/src/features/tickets/TicketCreatePage.tsx +++ b/frontend/src/features/tickets/TicketCreatePage.tsx @@ -1,9 +1,10 @@ import * as React from 'react' -import { Bot } from 'lucide-react' +import { AlertCircle, Bot, CheckCircle2, Lightbulb, Send } from 'lucide-react' import { AppField } from '@/components/app/AppField' import { AppPageHeader } from '@/components/app/AppPageHeader' import { AppSection } from '@/components/app/AppSection' +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' @@ -25,6 +26,7 @@ const initialForm: TicketInput = { export function TicketCreatePage() { const [form, setForm] = React.useState(initialForm) const [error, setError] = React.useState('') + const [submitting, setSubmitting] = React.useState(false) function updateField(field: Key, value: TicketInput[Key]) { setForm((current) => ({ ...current, [field]: value })) @@ -33,21 +35,35 @@ export function TicketCreatePage() { async function handleSubmit(event: React.FormEvent) { event.preventDefault() setError('') + setSubmitting(true) try { const ticket = await createTicket(form) window.location.hash = `/tickets/${ticket.id}` } catch (reason) { - setError(reason instanceof Error ? reason.message : '创建失败') + setError(reason instanceof Error ? reason.message : '创建工单失败,请检查必填信息或稍后重试。') + } finally { + setSubmitting(false) } } return (
-
- +
+
- + {error ? ( + + + 创建工单失败 + {error} + + ) : null} + + updateField('title', event.target.value)} /> -
- -