diff --git a/docs/SCREEN_CATALOG.md b/docs/SCREEN_CATALOG.md
index 5d4512d..935a07c 100644
--- a/docs/SCREEN_CATALOG.md
+++ b/docs/SCREEN_CATALOG.md
@@ -4,22 +4,21 @@ Figma 파일: [FOWOCO](https://www.figma.com/design/eaOD8OXZOGq6vK4H9pGXNi/FOWOC
상태 값: `design:needs-review`(디자인 검토 필요) / `design:figma-ready`(구현 가능) — 실제 상태는 담당자가 Figma 확인 후 갱신합니다.
-| # | 화면 | Figma 노드(참고) | 대상 | area 라벨 | 상태 |
-| --- | --- | --- | --- | --- | --- |
-| 1 | 로그인 | 19:2 | HR | `area:auth` | `design:needs-review` |
-| 2 | 대시보드 | 8:5 | HR | `area:dashboard` | `design:needs-review` |
-| 3 | 근로자 목록 관리 | 20:2 | HR | `area:worker` | `design:needs-review` |
-| 4 | 근로자 상세 / 계약 및 체류 관리 | 17:2 | HR | `area:worker` | `design:needs-review` |
-| 5a | 서류관리 / OCR | 22:2 | HR | `area:document` | `design:needs-review` |
-| 5b | 서류 초안 작성 화면 | 90:33 | HR | `area:document` | `design:needs-review` |
-| 5c | 서류 최종 결정 화면 | 98:9 | HR | `area:document` | `design:needs-review` |
-| 6a | 업무 카드 보드 | 17:42 | HR | `area:task-board` | `design:needs-review` |
-| 6b | 업무 카드 상세 / AI 승인 | 22:88 | HR | `area:task-board` | `design:needs-review` |
-| 7a | Agent 패널 | 34:2 | HR | `area:agent-panel` | `design:needs-review` |
-| 7b | AI 처리 이력 / 리포트 | 20:68 | HR | `area:agent-panel` | `design:needs-review` |
-| 8 | 티켓 관리 | 17:111 | HR | `area:ticket` | `design:needs-review` |
-| 9a | 근로자 모바일 안내 확인 | 22:142 | 근로자 | `area:mobile-worker` | `design:needs-review` |
-| 9b | 근로자 모바일 질문 작성 화면 | 217:68 | 근로자 | `area:mobile-worker` | `design:needs-review` |
+| # | 화면 | Figma 노드(참고) | 대상 | area 라벨 | 상태 |
+| --- | ------------------------------- | ---------------- | ------ | -------------------- | --------------------- |
+| 1 | 로그인 | 19:2 | HR | `area:auth` | `design:needs-review` |
+| 2 | 대시보드 | 8:5 | HR | `area:dashboard` | `design:needs-review` |
+| 3 | 근로자 목록 관리 | 20:2 | HR | `area:worker` | `design:needs-review` |
+| 4 | 근로자 상세 / 계약 및 체류 관리 | 17:2 | HR | `area:worker` | `design:needs-review` |
+| 5a | 서류관리 / OCR | 22:2 | HR | `area:document` | `design:needs-review` |
+| 5b | 서류 초안 작성 화면 | 90:33 | HR | `area:document` | `design:needs-review` |
+| 5c | 서류 최종 결정 화면 | 98:9 | HR | `area:document` | `design:needs-review` |
+| 6a | 업무 카드 보드 | 17:42 | HR | `area:task-board` | `design:needs-review` |
+| 6b | 업무 카드 상세 / AI 승인 | 22:88 | HR | `area:task-board` | `design:needs-review` |
+| 7a | Agent 패널 | 34:2 | HR | `area:agent-panel` | `design:needs-review` |
+| 7b | AI 처리 이력 / 리포트 | 20:68 | HR | `area:agent-panel` | `design:needs-review` |
+| 8a | 근로자 모바일 안내 확인 | 22:142 | 근로자 | `area:mobile-worker` | `design:needs-review` |
+| 8b | 근로자 모바일 질문 작성 화면 | 217:68 | 근로자 | `area:mobile-worker` | `design:needs-review` |
> 노드 ID는 와이어프레임 내 프레임을 빠르게 찾기 위한 참고용이며, 실제 구현 착수 전 담당자가 Figma에서 최신 상태를 다시 확인해야 합니다.
diff --git a/src/components/layout/AppLayout.tsx b/src/components/layout/AppLayout.tsx
index 3972ed3..26a0547 100644
--- a/src/components/layout/AppLayout.tsx
+++ b/src/components/layout/AppLayout.tsx
@@ -31,8 +31,6 @@ function getPageTitle(pathname: string) {
if (pathname === '/profile') return '설정'
if (pathname === '/workers') return '근로자 정보'
if (pathname.startsWith('/workers/')) return '근로자 정보'
- if (pathname.startsWith('/tickets/')) return '지원 요청 상세'
- if (pathname === '/tickets') return '지원 요청'
return 'FOWOCO'
}
diff --git a/src/components/layout/HelpModal/helpContent.ts b/src/components/layout/HelpModal/helpContent.ts
index 64fa266..87907a1 100644
--- a/src/components/layout/HelpModal/helpContent.ts
+++ b/src/components/layout/HelpModal/helpContent.ts
@@ -11,15 +11,18 @@ export const HELP_FAQ: HelpFaqItem[] = [
},
{
question: '승인은 누가 할 수 있나요?',
- answer: '설정 > 구성원과 승인 권한에서 지정된 구성원만 승인할 수 있습니다. Agent는 승인자가 될 수 없습니다.',
+ answer:
+ '설정 > 구성원과 승인 권한에서 지정된 구성원만 승인할 수 있습니다. Agent는 승인자가 될 수 없습니다.',
},
{
question: '근로자 서류는 어디서 확인하나요?',
- answer: '서류 메뉴에서 근로자별 제출 현황을 상태별(미제출/확인 대기/확인 완료)로 확인할 수 있습니다.',
+ answer:
+ '서류 메뉴에서 근로자별 제출 현황을 상태별(미제출/확인 대기/확인 완료)로 확인할 수 있습니다.',
},
{
question: '근로자가 남긴 질문은 어디서 답하나요?',
- answer: '티켓 메뉴에서 근로자 모바일로 들어온 질문·이슈를 신규/응답 대기/완료 탭으로 확인하고 답변합니다.',
+ answer:
+ '업무 상세의 소통 영역에서 근로자 모바일 응답과 제출 파일을 확인하고 후속 처리를 진행합니다.',
},
]
diff --git a/src/pages/IntroPage/introData.ts b/src/pages/IntroPage/introData.ts
index 393dc34..7310e75 100644
--- a/src/pages/IntroPage/introData.ts
+++ b/src/pages/IntroPage/introData.ts
@@ -36,13 +36,7 @@ export const HERO_HIGHLIGHTS: HeroHighlight[] = [
{ icon: 'lock', label: '전달·제출은 담당자가 직접 수행' },
]
-export type FeatureIconKey =
- | 'agent'
- | 'calendar'
- | 'contract'
- | 'folder'
- | 'globe'
- | 'radar'
+export type FeatureIconKey = 'agent' | 'calendar' | 'contract' | 'folder' | 'globe' | 'radar'
export interface Feature {
icon: FeatureIconKey
@@ -90,12 +84,16 @@ export interface Step {
}
// FOWOCO_Intent·Domain·Slot 학습가이드의 처리 파이프라인(Intent → Domain → Slot →
-// Workflow Resolver → Case/티켓)을 그대로 반영한 6단계.
+// Workflow Resolver → Case/Task)를 그대로 반영한 6단계.
export const STEPS: Step[] = [
{ no: '01', title: '자연어 요청', description: '"응웬반A 체류연장 준비해줘"처럼 편하게 요청' },
- { no: '02', title: 'Intent·Domain 분류', description: '무슨 일을, 어느 업무 영역에서 하려는지 파악' },
+ {
+ no: '02',
+ title: 'Intent·Domain 분류',
+ description: '무슨 일을, 어느 업무 영역에서 하려는지 파악',
+ },
{ no: '03', title: 'Slot 추출·검증', description: '근로자·날짜·서류 등 실행에 필요한 값 확인' },
- { no: '04', title: 'Case·티켓 생성', description: '새 Case 또는 기존 Case의 Subflow로 연결' },
+ { no: '04', title: 'Case·Task 생성', description: '새 Case와 실행할 업무 단계로 연결' },
{ no: '05', title: '담당자 검토·승인', description: '근거를 확인하고 최종 결정' },
{ no: '06', title: '직접 실행·기록', description: '전달·제출은 사람이, 결과는 Case에 기록' },
]
@@ -131,7 +129,8 @@ export const TRUST_ITEMS: TrustItem[] = [
},
]
-export type WorkflowIconKey = 'contract' | 'workerAdd' | 'exit' | 'folder' | 'payroll' | 'instruction'
+export type WorkflowIconKey =
+ 'contract' | 'workerAdd' | 'exit' | 'folder' | 'payroll' | 'instruction'
export interface Workflow {
icon: WorkflowIconKey
@@ -146,19 +145,22 @@ export const WORKFLOWS: Workflow[] = [
icon: 'contract',
kind: 'Master',
title: '재계약·연장',
- description: '계약·고용허가·취업활동·체류기간, 4개의 만료일을 각각 확인해 하나의 Case로 관리합니다.',
+ description:
+ '계약·고용허가·취업활동·체류기간, 4개의 만료일을 각각 확인해 하나의 Case로 관리합니다.',
},
{
icon: 'workerAdd',
kind: 'Master',
title: '신규등록',
- description: '여권·외국인등록증·계약서를 분석해 근로자 프로필 초안을 만들고 초기 업무를 생성합니다.',
+ description:
+ '여권·외국인등록증·계약서를 분석해 근로자 프로필 초안을 만들고 초기 업무를 생성합니다.',
},
{
icon: 'exit',
kind: 'Master',
title: '퇴사·고용변동',
- description: '사건 유형과 기준일을 확정하면 신고기한 후보를 계산하고 처리 결과를 Case에 남깁니다.',
+ description:
+ '사건 유형과 기준일을 확정하면 신고기한 후보를 계산하고 처리 결과를 Case에 남깁니다.',
},
{
icon: 'folder',
@@ -176,7 +178,8 @@ export const WORKFLOWS: Workflow[] = [
icon: 'instruction',
kind: 'Reusable',
title: '업무·근무일정 안내',
- description: '모호한 지시를 날짜·장소·수량이 분명한 문장으로 바꿔 쉬운 한국어와 모국어로 전달합니다.',
+ description:
+ '모호한 지시를 날짜·장소·수량이 분명한 문장으로 바꿔 쉬운 한국어와 모국어로 전달합니다.',
},
]
diff --git a/src/pages/TicketDetailPage/TicketDetailPage.module.css b/src/pages/TicketDetailPage/TicketDetailPage.module.css
deleted file mode 100644
index 8c45dc2..0000000
--- a/src/pages/TicketDetailPage/TicketDetailPage.module.css
+++ /dev/null
@@ -1,153 +0,0 @@
-.topBar {
- display: flex;
- align-items: center;
- margin: -32px -32px 0;
- padding: 19px 32px;
- background: var(--surface-default);
- border-bottom: 1px solid var(--border-default);
- font-size: 13px;
- color: var(--text-secondary);
-}
-
-@media (max-width: 768px) {
- .topBar {
- margin: -16px -16px 0;
- padding: 16px;
- }
-}
-
-.back {
- background: none;
- border: none;
- color: var(--text-secondary);
- font-size: 13px;
- cursor: pointer;
- padding: 0;
- text-decoration: none;
-}
-
-.headerRow {
- display: flex;
- align-items: center;
- flex-wrap: wrap;
- gap: 12px;
- margin-top: 34px;
-}
-
-.title {
- margin: 0;
- font-size: 26px;
- font-weight: 700;
- color: var(--text-primary);
-}
-
-.meta {
- margin: 12px 0 0;
- font-size: 13px;
- color: var(--text-secondary);
-}
-
-.sectionCard {
- margin-top: 16px;
- padding: var(--fowoco-spacing-16) var(--fowoco-spacing-24);
- background: var(--surface-default);
- border: 1px solid var(--border-default);
- border-radius: var(--fowoco-radius-10);
- box-shadow: var(--shadow-sm);
-}
-
-.cardTitle {
- margin: 0 0 16px;
- font-size: 18px;
- font-weight: 700;
- color: var(--text-primary);
-}
-
-.question {
- margin: 0;
- font-size: 14px;
- line-height: 22px;
- color: var(--text-primary);
-}
-
-.relatedCaseLink {
- background: none;
- border: none;
- padding: 0;
- font-size: 13px;
- font-weight: 500;
- color: var(--brand-primary);
- cursor: pointer;
-}
-
-.answerInput {
- width: 100%;
- box-sizing: border-box;
- padding: 12px 14px;
- background: var(--surface-subtle);
- border: 1px solid var(--border-default);
- border-radius: var(--fowoco-radius-6);
- font-size: 14px;
- font-family: inherit;
- color: var(--text-primary);
- resize: vertical;
-}
-
-.answerInput:focus-visible {
- outline: 2px solid var(--brand-primary);
- outline-offset: 2px;
-}
-
-.answerActions {
- display: flex;
- justify-content: flex-end;
- margin-top: 12px;
-}
-
-.sentList {
- display: flex;
- flex-direction: column;
- gap: 12px;
- margin-top: 16px;
- padding-top: 16px;
- border-top: 1px solid var(--border-default);
-}
-
-.sentRow {
- display: flex;
- align-items: flex-start;
- gap: 16px;
-}
-
-.sentAt {
- flex-shrink: 0;
- width: 50px;
- font-size: 12px;
- color: var(--text-secondary);
-}
-
-.sentText {
- flex: 1;
- margin: 0;
- font-size: 13px;
- color: var(--text-primary);
-}
-
-.statusDock {
- display: flex;
- align-items: center;
- gap: 24px;
- margin-top: 16px;
- padding: var(--fowoco-spacing-12) var(--fowoco-spacing-20);
- background: var(--surface-default);
- border: 1px solid var(--border-default);
- border-radius: var(--fowoco-radius-10);
- box-shadow: var(--shadow-sm);
-}
-
-.statusNote {
- flex: 1;
- font-size: 13px;
- font-weight: 500;
- color: var(--text-secondary);
-}
diff --git a/src/pages/TicketDetailPage/TicketDetailPage.test.tsx b/src/pages/TicketDetailPage/TicketDetailPage.test.tsx
deleted file mode 100644
index 111ed53..0000000
--- a/src/pages/TicketDetailPage/TicketDetailPage.test.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import { render, screen } from '@testing-library/react'
-import userEvent from '@testing-library/user-event'
-import { MemoryRouter, Route, Routes } from 'react-router-dom'
-import { describe, expect, it } from 'vitest'
-import { TicketDetailPage } from './TicketDetailPage'
-import { TICKETS } from '../TicketListPage/ticketListData'
-
-function renderPage(ticketId: string) {
- render(
-
-
- } />
- 티켓 목록
} />
- 업무 상세} />
-
- ,
- )
-}
-
-describe('TicketDetailPage', () => {
- it('renders the question and worker name for the selected ticket', () => {
- const ticket = TICKETS[0]
- renderPage(ticket.id)
-
- expect(screen.getByRole('heading', { name: ticket.workerName })).toBeInTheDocument()
- expect(screen.getByText(ticket.question)).toBeInTheDocument()
- })
-
- it('navigates to the related task when one exists', async () => {
- const user = userEvent.setup()
- const ticket = TICKETS.find((item) => item.relatedCaseId !== null)!
- renderPage(ticket.id)
-
- await user.click(screen.getByRole('button', { name: `${ticket.relatedCaseId} 업무 상세 열기 →` }))
-
- expect(await screen.findByText('업무 상세')).toBeInTheDocument()
- })
-
- it('shows an empty state when there is no related task', () => {
- const ticket = TICKETS.find((item) => item.relatedCaseId === null)!
- renderPage(ticket.id)
-
- expect(screen.getByText('연결된 업무가 없습니다')).toBeInTheDocument()
- })
-
- it('sends an answer and advances a 신규 ticket to 응답 대기', async () => {
- const user = userEvent.setup()
- const ticket = TICKETS.find((item) => item.status === 'new')!
- renderPage(ticket.id)
-
- await user.type(screen.getByPlaceholderText('근로자에게 보낼 답변을 입력하세요'), '확인 후 안내드리겠습니다.')
- await user.click(screen.getByRole('button', { name: '답변 발송' }))
-
- expect(screen.getByText('확인 후 안내드리겠습니다.')).toBeInTheDocument()
- expect(screen.getByText('응답 대기')).toBeInTheDocument()
- })
-
- it('advances status via the status action button', async () => {
- const user = userEvent.setup()
- const ticket = TICKETS.find((item) => item.status === 'waiting')!
- renderPage(ticket.id)
-
- await user.click(screen.getByRole('button', { name: '완료 처리' }))
-
- expect(screen.getByText('현재 상태: 완료')).toBeInTheDocument()
- })
-
- it('falls back to the first ticket when the ticketId is invalid', () => {
- renderPage('does-not-exist')
-
- expect(screen.getByRole('heading', { name: TICKETS[0].workerName })).toBeInTheDocument()
- })
-})
diff --git a/src/pages/TicketDetailPage/TicketDetailPage.tsx b/src/pages/TicketDetailPage/TicketDetailPage.tsx
deleted file mode 100644
index 25f3d3b..0000000
--- a/src/pages/TicketDetailPage/TicketDetailPage.tsx
+++ /dev/null
@@ -1,135 +0,0 @@
-import { useState } from 'react'
-import { Link, useNavigate, useParams } from 'react-router-dom'
-import { Button } from '../../components/ui/Button/Button'
-import { EmptyState } from '../../components/ui/EmptyState/EmptyState'
-import { StatusLabel } from '../../components/ui/StatusLabel/StatusLabel'
-import { useToastStore } from '../../store/toastStore'
-import styles from './TicketDetailPage.module.css'
-import {
- TICKET_STATUS_LABEL,
- TICKET_STATUS_TONE,
- TICKETS,
- type TicketStatus,
-} from '../TicketListPage/ticketListData'
-
-const NEXT_STATUS: Record = {
- new: 'waiting',
- waiting: 'done',
- done: null,
-}
-
-const NEXT_STATUS_ACTION_LABEL: Record = {
- new: '응답 대기로 전환',
- waiting: '완료 처리',
- done: '완료됨',
-}
-
-interface SentAnswer {
- id: string
- text: string
- sentAt: string
-}
-
-export function TicketDetailPage() {
- const { ticketId } = useParams()
- const navigate = useNavigate()
- const showToast = useToastStore((state) => state.showToast)
-
- const ticket = TICKETS.find((item) => item.id === ticketId) ?? TICKETS[0]
-
- const [status, setStatus] = useState(ticket.status)
- const [draft, setDraft] = useState('')
- const [sentAnswers, setSentAnswers] = useState([])
-
- function handleSendAnswer() {
- const text = draft.trim()
- if (!text) return
-
- // TODO(backend): POST /api/tickets/:id/answers { text } -> 근로자 모바일로 답변 발송
- setSentAnswers((prev) => [...prev, { id: crypto.randomUUID(), text, sentAt: '방금' }])
- setDraft('')
- showToast('답변을 발송했습니다.')
- setStatus((current) => (current === 'new' ? 'waiting' : current))
- }
-
- function handleAdvanceStatus() {
- const next = NEXT_STATUS[status]
- if (!next) return
-
- // TODO(backend): PATCH /api/tickets/:id { status } -> 상태 전환
- setStatus(next)
- showToast(`상태를 "${TICKET_STATUS_LABEL[next]}"(으)로 변경했습니다.`)
- }
-
- return (
-
-
-
- ← 티켓
-
-
-
-
-
{ticket.workerName}
- {TICKET_STATUS_LABEL[status]}
-
-
접수 {ticket.receivedAt}
-
-
-
질문 원문
-
{ticket.question}
-
-
-
-
관련 업무
- {ticket.relatedCaseId ? (
-
- ) : (
-
- )}
-
-
-
-
-
-
- 현재 상태: {TICKET_STATUS_LABEL[status]}
-
-
-
-
- )
-}
diff --git a/src/pages/TicketListPage/TicketListPage.module.css b/src/pages/TicketListPage/TicketListPage.module.css
deleted file mode 100644
index 4623cae..0000000
--- a/src/pages/TicketListPage/TicketListPage.module.css
+++ /dev/null
@@ -1,68 +0,0 @@
-.headline {
- margin: 0;
- font-size: 26px;
- font-weight: 700;
- color: var(--text-primary);
-}
-
-.description {
- margin: 8px 0 0;
- font-size: 14px;
- color: var(--text-secondary);
-}
-
-.stateWrap {
- margin-top: 24px;
-}
-
-.list {
- display: flex;
- flex-direction: column;
- gap: 12px;
- margin-top: 24px;
-}
-
-.rowMain {
- min-width: 0;
-}
-
-.workerName {
- margin: 0;
- font-size: 14px;
- font-weight: 500;
- color: var(--text-primary);
-}
-
-.summary {
- margin: 4px 0 0;
- font-size: 12px;
- color: var(--text-secondary);
-}
-
-.receivedAt {
- font-size: 13px;
- color: var(--text-secondary);
-}
-
-.answerButton {
- background: none;
- border: none;
- padding: 0;
- font-size: 13px;
- font-weight: 500;
- color: var(--brand-primary);
- cursor: pointer;
- justify-self: end;
-}
-
-@media (max-width: 720px) {
- .answerButton {
- justify-self: start;
- }
-}
-
-.footerText {
- margin: 16px 0 0;
- font-size: 12px;
- color: var(--text-secondary);
-}
diff --git a/src/pages/TicketListPage/TicketListPage.test.tsx b/src/pages/TicketListPage/TicketListPage.test.tsx
deleted file mode 100644
index 0b8cede..0000000
--- a/src/pages/TicketListPage/TicketListPage.test.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import { render, screen } from '@testing-library/react'
-import userEvent from '@testing-library/user-event'
-import { MemoryRouter, Route, Routes } from 'react-router-dom'
-import { describe, expect, it } from 'vitest'
-import { TicketListPage } from './TicketListPage'
-import { TICKET_TABS, TICKETS } from './ticketListData'
-
-function renderPage(demoState = 'success') {
- render(
-
-
- } />
- 티켓 상세} />
-
- ,
- )
-}
-
-describe('TicketListPage', () => {
- it('renders every tab and defaults to the 신규 tab tickets', () => {
- renderPage()
-
- for (const tab of TICKET_TABS) {
- expect(screen.getByRole('tab', { name: `${tab.label} ${tab.count}` })).toBeInTheDocument()
- }
- const newTickets = TICKETS.filter((ticket) => ticket.status === 'new')
- for (const ticket of newTickets) {
- expect(screen.getByText(ticket.summary)).toBeInTheDocument()
- }
- })
-
- it('switches tickets when a different tab is selected', async () => {
- const user = userEvent.setup()
- renderPage()
-
- await user.click(screen.getByRole('tab', { name: '완료 6' }))
-
- const doneTicket = TICKETS.find((ticket) => ticket.status === 'done')
- expect(screen.getByText(doneTicket!.summary)).toBeInTheDocument()
- expect(screen.queryByText(TICKETS[0].summary)).not.toBeInTheDocument()
- })
-
- it('navigates to the ticket detail when "답변하기 →" is clicked', async () => {
- const user = userEvent.setup()
- renderPage()
-
- await user.click(screen.getAllByRole('button', { name: '답변하기 →' })[0])
-
- expect(await screen.findByText('티켓 상세')).toBeInTheDocument()
- })
-
- it('shows a loading state', () => {
- renderPage('loading')
- expect(screen.getByText('티켓 목록을 불러오는 중입니다')).toBeInTheDocument()
- })
-
- it('shows an error state', () => {
- renderPage('error')
- expect(screen.getByText('티켓 목록을 불러오지 못했습니다')).toBeInTheDocument()
- })
-})
diff --git a/src/pages/TicketListPage/TicketListPage.tsx b/src/pages/TicketListPage/TicketListPage.tsx
deleted file mode 100644
index e2ec4a7..0000000
--- a/src/pages/TicketListPage/TicketListPage.tsx
+++ /dev/null
@@ -1,104 +0,0 @@
-import { useMemo, useState } from 'react'
-import { useNavigate } from 'react-router-dom'
-import { EmptyState } from '../../components/ui/EmptyState/EmptyState'
-import { ListRow } from '../../components/ui/ListRow/ListRow'
-import { StatusLabel } from '../../components/ui/StatusLabel/StatusLabel'
-import { Tabs } from '../../components/ui/Tabs/Tabs'
-import { useAsyncDemoData } from '../../hooks/useAsyncDemoData'
-import styles from './TicketListPage.module.css'
-import {
- TICKET_STATUS_LABEL,
- TICKET_STATUS_TONE,
- TICKET_TABS,
- TICKETS,
- TOTAL_TICKET_COUNT,
-} from './ticketListData'
-
-export function TicketListPage() {
- const navigate = useNavigate()
- const status = useAsyncDemoData(TICKETS.length === 0)
- const [activeTab, setActiveTab] = useState(TICKET_TABS[0].id)
-
- const visibleTickets = useMemo(
- () => TICKETS.filter((ticket) => ticket.status === activeTab),
- [activeTab],
- )
-
- function handleAnswerTicket(ticketId: string) {
- navigate(`/tickets/${ticketId}`)
- }
-
- return (
-
-
근로자 문의 티켓
-
- 근로자 모바일에서 들어온 질문·이슈를 확인하고 답변합니다.
-
-
-
-
- {status === 'loading' && (
-
-
-
- )}
-
- {status === 'error' && (
-
-
-
- )}
-
- {status === 'empty' && (
-
-
-
- )}
-
- {status === 'success' && (
- <>
- {visibleTickets.length === 0 ? (
-
-
-
- ) : (
-
- {visibleTickets.map((ticket) => (
-
-
-
{ticket.workerName}
-
{ticket.summary}
-
-
- {TICKET_STATUS_LABEL[ticket.status]}
-
- {ticket.receivedAt}
-
-
- ))}
-
- )}
-
-
- {TOTAL_TICKET_COUNT}건 중 {visibleTickets.length}건 표시
-
- >
- )}
-
- )
-}
diff --git a/src/pages/TicketListPage/ticketListData.ts b/src/pages/TicketListPage/ticketListData.ts
deleted file mode 100644
index e1b94db..0000000
--- a/src/pages/TicketListPage/ticketListData.ts
+++ /dev/null
@@ -1,92 +0,0 @@
-import type { StatusTone } from '../../components/ui/StatusLabel/StatusLabel'
-
-// TODO(backend): GET /api/tickets?tab= -> TICKET_TABS, TICKETS 대체
-// LINK-001/002의 "질문이 있습니다" 버튼(TODO(backend))이 실제로 생성할 데이터가 여기서 소비된다.
-
-export interface TicketTab {
- id: string
- label: string
- count: number
-}
-
-export const TICKET_TABS: TicketTab[] = [
- { id: 'new', label: '신규', count: 2 },
- { id: 'waiting', label: '응답 대기', count: 3 },
- { id: 'done', label: '완료', count: 6 },
-]
-
-export type TicketStatus = 'new' | 'waiting' | 'done'
-
-export const TICKET_STATUS_TONE: Record = {
- new: 'critical',
- waiting: 'warning',
- done: 'success',
-}
-
-export const TICKET_STATUS_LABEL: Record = {
- new: '신규',
- waiting: '응답 대기',
- done: '완료',
-}
-
-export interface TicketItem {
- id: string
- workerName: string
- summary: string
- status: TicketStatus
- receivedAt: string
- /** 근로자 질문 원문 (목록에는 summary로 축약해 보여준다) */
- question: string
- /** 이 티켓과 관련된 업무. 없으면 null. */
- relatedCaseId: string | null
-}
-
-export const TICKETS: TicketItem[] = [
- {
- id: 'TICKET-1',
- workerName: '수라즈C',
- summary: '기숙사 인터넷이 이틀째 연결되지 않습니다.',
- status: 'new',
- receivedAt: '오늘 08:20',
- question: '기숙사 인터넷이 이틀째 연결되지 않습니다. 담당자님 확인 부탁드립니다.',
- relatedCaseId: 'WI-032',
- },
- {
- id: 'TICKET-2',
- workerName: '쩐티B',
- summary: '체류연장 서류 제출 방법을 다시 안내받고 싶습니다.',
- status: 'new',
- receivedAt: '오늘 07:55',
- question: '체류연장 서류를 어디에 어떻게 제출해야 하는지 다시 한번 안내받고 싶습니다.',
- relatedCaseId: 'WI-018',
- },
- {
- id: 'TICKET-3',
- workerName: '아흐메드D',
- summary: '급여명세서에서 공제 항목 문의드립니다.',
- status: 'waiting',
- receivedAt: '어제 18:40',
- question: '이번 달 급여명세서에서 공제된 항목이 지난달과 달라서 문의드립니다.',
- relatedCaseId: null,
- },
- {
- id: 'TICKET-4',
- workerName: '응웬반A',
- summary: '건강검진 예약 일정을 변경할 수 있는지 문의합니다.',
- status: 'waiting',
- receivedAt: '어제 15:10',
- question: '건강검진 예약 일정을 다음 주로 변경할 수 있을까요?',
- relatedCaseId: 'WI-021',
- },
- {
- id: 'TICKET-5',
- workerName: '박서준',
- summary: '외국인등록증 재발급 절차를 안내받았습니다.',
- status: 'done',
- receivedAt: '2026-07-17',
- question: '외국인등록증을 분실해서 재발급 절차가 궁금합니다.',
- relatedCaseId: null,
- },
-]
-
-export const TOTAL_TICKET_COUNT = 11
diff --git a/src/routes.tsx b/src/routes.tsx
index 6d5f3d0..aba1a26 100644
--- a/src/routes.tsx
+++ b/src/routes.tsx
@@ -51,16 +51,6 @@ const ReviewWorkPage = lazy(() =>
default: module.ReviewWorkPage,
})),
)
-const TicketDetailPage = lazy(() =>
- import('./pages/TicketDetailPage/TicketDetailPage').then((module) => ({
- default: module.TicketDetailPage,
- })),
-)
-const TicketListPage = lazy(() =>
- import('./pages/TicketListPage/TicketListPage').then((module) => ({
- default: module.TicketListPage,
- })),
-)
const WorkerDetailPage = lazy(() =>
import('./pages/WorkerDetailPage/WorkerDetailPage').then((module) => ({
default: module.WorkerDetailPage,
@@ -104,8 +94,6 @@ export const router = createBrowserRouter([
{ path: '/tasks/new/review', element: },
{ path: '/tasks/:taskId', element: },
{ path: '/agent', element: },
- { path: '/tickets', element: },
- { path: '/tickets/:ticketId', element: },
{ path: '/profile', element: },
],
},