实现V4订单详情总览页
This commit is contained in:
@@ -454,6 +454,43 @@ export default {
|
||||
taskQueue: 'Order task queue',
|
||||
v4TaskTimeline: 'V4 order task timeline',
|
||||
v4TaskTimelineEmpty: 'No V4 order tasks.',
|
||||
v4TaskTimelineHint: 'Shows safe order-task and card summaries only. Complete actions in the V4 task detail page.',
|
||||
v4OverviewTitle: 'Current V4 confirmed snapshot',
|
||||
v4OverviewSubtitle: 'Derived from confirmed cards only, not from unconfirmed AI suggestions.',
|
||||
v4OverviewEmpty: 'No confirmed V4 order snapshot yet.',
|
||||
accountCode: 'Account Code',
|
||||
accountName: 'Account Name',
|
||||
marketCode: 'Market Code',
|
||||
sourceCode: 'Source Code',
|
||||
departureDate: 'Departure date',
|
||||
rateCode: 'Rate Code',
|
||||
roomItems: 'Confirmed room items',
|
||||
latestConfirmedAt: 'Latest confirmed at',
|
||||
traceCardStatus: 'Trace card status',
|
||||
roomingListCardStatus: 'Rooming List card status',
|
||||
paymentCardStatus: 'Payment card status',
|
||||
nextV4ActionTitle: 'Next action',
|
||||
nextV4ActionSubtitle: 'The order overview only links to the task page; it does not confirm, review, or edit cards.',
|
||||
nextV4ActionType: 'Action type',
|
||||
nextV4ActionStatus: 'Action status',
|
||||
nextV4CardId: 'Card ID',
|
||||
openOrderTaskCount: 'Open V4 tasks',
|
||||
enterV4Task: 'Open task page',
|
||||
nextActionConfirm: 'Confirm card',
|
||||
nextActionReview: 'Manual review',
|
||||
nextActionNone: 'No pending action',
|
||||
nextV4ActionEmpty: 'No next V4 action.',
|
||||
relatedSourceMessages: 'Related source messages',
|
||||
relatedSourceMessagesHint: 'Shows headers and conversation summary only. Open the conversation page for full email content.',
|
||||
relatedSourceMessagesEmpty: 'No related source messages.',
|
||||
externalConversationId: 'External conversation ID',
|
||||
latestActivityAt: 'Latest activity',
|
||||
v4CardsEmpty: 'No card summaries.',
|
||||
cardId: 'Card ID',
|
||||
reviewStatus: 'Review status',
|
||||
confirmedBy: 'Confirmed by',
|
||||
confirmedAt: 'Confirmed at',
|
||||
sourceEventIndex: 'Source event {index}',
|
||||
sourceMessage: 'Source message',
|
||||
businessKeySource: 'Business key source',
|
||||
updatedAt: 'Last updated',
|
||||
@@ -822,6 +859,12 @@ export default {
|
||||
INFORMATIONAL_MESSAGE: 'Message notification (legacy)',
|
||||
SOURCE_MESSAGE_REVIEW_NOTIFICATION: 'Source message notification',
|
||||
SOURCE_MESSAGE_ONLY: 'Source message only',
|
||||
SOURCE_MESSAGE_DISPLAY: 'Source message display card',
|
||||
SOURCE_MESSAGE_NOTIFICATION: 'Source message notification card',
|
||||
BASIC_INFORMATION: 'Basic Information card',
|
||||
ROOM_INFORMATION: 'Room Information card',
|
||||
TRACE_RESERVATION_NOTES: 'Trace notes card',
|
||||
PAYMENT: 'Payment card',
|
||||
MANUAL_REVIEW: 'Manual review',
|
||||
FALLBACK_REVIEW: 'Manual review',
|
||||
},
|
||||
|
||||
@@ -454,6 +454,43 @@ export default {
|
||||
taskQueue: 'คิวงานของออเดอร์นี้',
|
||||
v4TaskTimeline: 'ไทม์ไลน์งานออเดอร์ V4',
|
||||
v4TaskTimelineEmpty: 'ยังไม่มีงานออเดอร์ V4',
|
||||
v4TaskTimelineHint: 'แสดงเฉพาะสรุปงานและการ์ดที่ปลอดภัย หากต้องดำเนินการให้เข้าไปที่หน้ารายละเอียดงาน V4',
|
||||
v4OverviewTitle: 'สแนปช็อต V4 ที่ยืนยันแล้ว',
|
||||
v4OverviewSubtitle: 'มาจากการ์ดที่ยืนยันแล้วเท่านั้น ไม่ใช้คำแนะนำ AI ที่ยังไม่ยืนยัน',
|
||||
v4OverviewEmpty: 'ยังไม่มีสแนปช็อตออเดอร์ V4 ที่ยืนยันแล้ว',
|
||||
accountCode: 'Account Code',
|
||||
accountName: 'Account Name',
|
||||
marketCode: 'Market Code',
|
||||
sourceCode: 'Source Code',
|
||||
departureDate: 'วันออก',
|
||||
rateCode: 'Rate Code',
|
||||
roomItems: 'ประเภทห้องและจำนวนที่ยืนยันแล้ว',
|
||||
latestConfirmedAt: 'เวลายืนยันล่าสุด',
|
||||
traceCardStatus: 'สถานะการ์ด Trace',
|
||||
roomingListCardStatus: 'สถานะการ์ด Rooming List',
|
||||
paymentCardStatus: 'สถานะการ์ด Payment',
|
||||
nextV4ActionTitle: 'ขั้นตอนถัดไป',
|
||||
nextV4ActionSubtitle: 'หน้าสรุปออเดอร์เป็นทางเข้าเท่านั้น ไม่ยืนยัน ตรวจสอบ หรือแก้ไขการ์ดในหน้านี้',
|
||||
nextV4ActionType: 'ประเภทการดำเนินการ',
|
||||
nextV4ActionStatus: 'สถานะการดำเนินการ',
|
||||
nextV4CardId: 'รหัสการ์ด',
|
||||
openOrderTaskCount: 'งาน V4 ที่ยังไม่เสร็จ',
|
||||
enterV4Task: 'ไปหน้าดำเนินการ',
|
||||
nextActionConfirm: 'ยืนยันการ์ด',
|
||||
nextActionReview: 'ตรวจสอบด้วยคน',
|
||||
nextActionNone: 'ไม่มีงานรอดำเนินการ',
|
||||
nextV4ActionEmpty: 'ยังไม่มีขั้นตอน V4 ถัดไป',
|
||||
relatedSourceMessages: 'ข้อความต้นทางที่เกี่ยวข้อง',
|
||||
relatedSourceMessagesHint: 'แสดงเฉพาะหัวอีเมลและสรุปเธรด หากต้องการอ่านทั้งหมดให้เปิดหน้าบทสนทนาอีเมล',
|
||||
relatedSourceMessagesEmpty: 'ยังไม่มีข้อความต้นทางที่เกี่ยวข้อง',
|
||||
externalConversationId: 'รหัสเธรดภายนอก',
|
||||
latestActivityAt: 'กิจกรรมล่าสุด',
|
||||
v4CardsEmpty: 'ยังไม่มีสรุปการ์ด',
|
||||
cardId: 'รหัสการ์ด',
|
||||
reviewStatus: 'สถานะตรวจสอบ',
|
||||
confirmedBy: 'ผู้ยืนยัน',
|
||||
confirmedAt: 'เวลายืนยัน',
|
||||
sourceEventIndex: 'เหตุการณ์ต้นทาง {index}',
|
||||
sourceMessage: 'ข้อความต้นทาง',
|
||||
businessKeySource: 'แหล่งที่มาของเลขธุรกิจ',
|
||||
updatedAt: 'อัปเดตล่าสุด',
|
||||
@@ -822,6 +859,12 @@ export default {
|
||||
INFORMATIONAL_MESSAGE: 'แจ้งเตือนข้อความ (เดิม)',
|
||||
SOURCE_MESSAGE_REVIEW_NOTIFICATION: 'แจ้งเตือนข้อความต้นทาง',
|
||||
SOURCE_MESSAGE_ONLY: 'ข้อความต้นทางแบบอ่านอย่างเดียว',
|
||||
SOURCE_MESSAGE_DISPLAY: 'การ์ดแสดงข้อความต้นทาง',
|
||||
SOURCE_MESSAGE_NOTIFICATION: 'การ์ดแจ้งเตือนข้อความต้นทาง',
|
||||
BASIC_INFORMATION: 'การ์ดข้อมูลพื้นฐาน',
|
||||
ROOM_INFORMATION: 'การ์ดข้อมูลห้อง',
|
||||
TRACE_RESERVATION_NOTES: 'การ์ดหมายเหตุติดตาม',
|
||||
PAYMENT: 'การ์ดการชำระเงิน',
|
||||
MANUAL_REVIEW: 'ตรวจสอบด้วยคน',
|
||||
FALLBACK_REVIEW: 'ตรวจสอบด้วยคน',
|
||||
},
|
||||
|
||||
@@ -454,6 +454,43 @@ export default {
|
||||
taskQueue: '同订单任务队列',
|
||||
v4TaskTimeline: 'V4 订单任务时间线',
|
||||
v4TaskTimelineEmpty: '暂无 V4 订单任务。',
|
||||
v4TaskTimelineHint: '只展示订单任务和任务卡安全摘要;处理动作请进入 V4 任务详情页完成。',
|
||||
v4OverviewTitle: 'V4 当前确认快照',
|
||||
v4OverviewSubtitle: '仅来自已确认任务卡,不使用未确认 AI 建议。',
|
||||
v4OverviewEmpty: '暂无已确认的 V4 订单快照。',
|
||||
accountCode: 'Account 代码',
|
||||
accountName: 'Account 名称',
|
||||
marketCode: 'Market 代码',
|
||||
sourceCode: 'Source 代码',
|
||||
departureDate: '离店日期',
|
||||
rateCode: 'Rate Code',
|
||||
roomItems: '已确认房型房量',
|
||||
latestConfirmedAt: '最近确认时间',
|
||||
traceCardStatus: 'Trace 卡片状态',
|
||||
roomingListCardStatus: 'Rooming List 卡片状态',
|
||||
paymentCardStatus: 'Payment 卡片状态',
|
||||
nextV4ActionTitle: '下一步处理',
|
||||
nextV4ActionSubtitle: '订单总览只提供入口,不直接确认、复核或编辑任务卡。',
|
||||
nextV4ActionType: '动作类型',
|
||||
nextV4ActionStatus: '动作状态',
|
||||
nextV4CardId: '任务卡编号',
|
||||
openOrderTaskCount: '未完成 V4 任务数',
|
||||
enterV4Task: '进入处理页',
|
||||
nextActionConfirm: '确认任务卡',
|
||||
nextActionReview: '人工复核',
|
||||
nextActionNone: '无待处理动作',
|
||||
nextV4ActionEmpty: '暂无下一步 V4 处理动作。',
|
||||
relatedSourceMessages: '关联来源消息',
|
||||
relatedSourceMessagesHint: '只展示邮件头和会话摘要;全文请进入邮件会话页查看。',
|
||||
relatedSourceMessagesEmpty: '暂无关联来源消息。',
|
||||
externalConversationId: '外部会话编号',
|
||||
latestActivityAt: '最近活动时间',
|
||||
v4CardsEmpty: '暂无任务卡摘要。',
|
||||
cardId: '任务卡编号',
|
||||
reviewStatus: '复核状态',
|
||||
confirmedBy: '确认人',
|
||||
confirmedAt: '确认时间',
|
||||
sourceEventIndex: '来源事件 {index}',
|
||||
sourceMessage: '来源消息',
|
||||
businessKeySource: '业务号来源',
|
||||
updatedAt: '最近更新',
|
||||
@@ -822,6 +859,12 @@ export default {
|
||||
INFORMATIONAL_MESSAGE: '消息通知(历史 Message Notification)',
|
||||
SOURCE_MESSAGE_REVIEW_NOTIFICATION: '来源消息通知',
|
||||
SOURCE_MESSAGE_ONLY: '来源消息只读',
|
||||
SOURCE_MESSAGE_DISPLAY: '来源消息展示卡',
|
||||
SOURCE_MESSAGE_NOTIFICATION: '来源消息通知卡',
|
||||
BASIC_INFORMATION: '基础信息卡',
|
||||
ROOM_INFORMATION: '房型信息卡',
|
||||
TRACE_RESERVATION_NOTES: '追踪备注卡',
|
||||
PAYMENT: '付款信息卡',
|
||||
MANUAL_REVIEW: '人工复核',
|
||||
FALLBACK_REVIEW: '人工复核',
|
||||
},
|
||||
|
||||
@@ -11,6 +11,7 @@ import ReservationSourceMessageConversationView from '@/views/reservation/Reserv
|
||||
import ReservationTaskListView from '@/views/reservation/ReservationTaskListView.vue'
|
||||
import { useAuthStore } from '@/stores/authStore'
|
||||
import type {
|
||||
ReservationOrderDetailResult,
|
||||
ReservationOrderListResult,
|
||||
ReservationV4OrderTaskListResult,
|
||||
ReservationV4WorkbenchListResult,
|
||||
@@ -221,7 +222,7 @@ function createV4OrderTaskListResult(
|
||||
}
|
||||
}
|
||||
|
||||
function createOrderDetailResult(displayName = 'GRP-001') {
|
||||
function createOrderDetailResult(displayName = 'GRP-001'): ReservationOrderDetailResult {
|
||||
return {
|
||||
order: {
|
||||
order_id: displayName === 'GRP-002' ? '20002' : '20001',
|
||||
@@ -286,6 +287,49 @@ function createOrderDetailResult(displayName = 'GRP-001') {
|
||||
review_required_count: 1,
|
||||
confirmed_count: 0,
|
||||
},
|
||||
cards: [
|
||||
{
|
||||
card_id: '9100',
|
||||
card_type: 'SOURCE_MESSAGE_DISPLAY',
|
||||
event_type: null,
|
||||
source_event_index: 0,
|
||||
card_sort_order: 0,
|
||||
card_status: 'READONLY',
|
||||
review_status: null,
|
||||
confirmed_by: null,
|
||||
confirmed_at: null,
|
||||
latest_activity_at: '2026-07-08T05:05:00Z',
|
||||
ai_payload_json: 'unsafe ai payload must stay hidden',
|
||||
display_payload_json: 'unsafe display payload must stay hidden',
|
||||
confirmed_payload_json: 'unsafe confirmed payload must stay hidden',
|
||||
text_body: 'unsafe mail body must stay hidden',
|
||||
attachment_url: 'https://oss.example.test/private.xlsx',
|
||||
},
|
||||
{
|
||||
card_id: '9101',
|
||||
card_type: 'BASIC_INFORMATION',
|
||||
event_type: 'NEW_BOOKING',
|
||||
source_event_index: 1,
|
||||
card_sort_order: 10,
|
||||
card_status: 'CONFIRMED',
|
||||
review_status: null,
|
||||
confirmed_by: 'frontend-query-admin',
|
||||
confirmed_at: '2026-07-08T11:10:00Z',
|
||||
latest_activity_at: '2026-07-08T11:10:00Z',
|
||||
},
|
||||
{
|
||||
card_id: '9104',
|
||||
card_type: 'PAYMENT',
|
||||
event_type: 'PAYMENT',
|
||||
source_event_index: 3,
|
||||
card_sort_order: 60,
|
||||
card_status: 'REVIEW_REQUIRED',
|
||||
review_status: 'PENDING',
|
||||
confirmed_by: null,
|
||||
confirmed_at: null,
|
||||
latest_activity_at: '2026-07-08T11:20:00Z',
|
||||
},
|
||||
],
|
||||
source_message_summary: {
|
||||
source_message_id: '30003',
|
||||
hotel_id: 'HOTEL-TEST',
|
||||
@@ -303,8 +347,54 @@ function createOrderDetailResult(displayName = 'GRP-001') {
|
||||
latest_activity_at: '2026-07-08T05:10:00Z',
|
||||
},
|
||||
],
|
||||
order_overview: {
|
||||
account_code: 'QBD_TRAVEL',
|
||||
account_name: 'Q.B.D. TRAVEL GROUP CO., LTD',
|
||||
market_code: 'LEISURE',
|
||||
source_code: 'TRAVEL_AGENT',
|
||||
arrival_date: '2026-07-26',
|
||||
departure_date: '2026-07-29',
|
||||
rate_code: 'BAR',
|
||||
room_items: [
|
||||
{
|
||||
room_type_code: 'RM1',
|
||||
room_count: 2,
|
||||
},
|
||||
{
|
||||
room_type_code: 'RM2',
|
||||
room_count: 1,
|
||||
},
|
||||
],
|
||||
trace_card_status: 'CONFIRMED',
|
||||
rooming_list_card_status: 'PENDING_CONFIRM',
|
||||
payment_card_status: 'REVIEW_REQUIRED',
|
||||
latest_confirmed_at: '2026-07-08T11:20:00Z',
|
||||
ai_payload_json: 'unsafe overview payload must stay hidden',
|
||||
},
|
||||
next_v4_action: {
|
||||
order_task_id: '9001',
|
||||
card_id: '9104',
|
||||
action_type: 'REVIEW',
|
||||
action_status: 'REVIEW_REQUIRED',
|
||||
open_order_task_count: 1,
|
||||
},
|
||||
related_source_messages: [
|
||||
{
|
||||
source_message_id: '30003',
|
||||
hotel_id: 'HOTEL-TEST',
|
||||
external_message_id: 'm-v4-1',
|
||||
external_conversation_id: 'thread-v4-1',
|
||||
subject: 'V4 Booking Package',
|
||||
sender_summary: 'v4@example.test',
|
||||
received_at: '2026-07-08T05:00:00Z',
|
||||
source_sent_at: null,
|
||||
conversation_message_count: 1,
|
||||
html_body: '<strong>unsafe related mail body</strong>',
|
||||
attachment_url: 'https://oss.example.test/related.xlsx',
|
||||
},
|
||||
],
|
||||
warnings: [],
|
||||
}
|
||||
} as unknown as ReservationOrderDetailResult
|
||||
}
|
||||
|
||||
function createSourceMessageConversationResult(
|
||||
@@ -480,28 +570,65 @@ describe('reservation P0 views', () => {
|
||||
vi.mocked(service.fetchSourceMessageConversation).mockReset()
|
||||
})
|
||||
|
||||
it('renders active order task source evidence without the stale pending copy', async () => {
|
||||
it('renders the V4 order overview, next action, related source messages and safe card timeline', async () => {
|
||||
vi.mocked(service.fetchReservationOrderDetail).mockResolvedValue(createOrderDetailResult())
|
||||
|
||||
const wrapper = await mountWithPlugins(ReservationOrderDetailView, '/reservation/orders/20001', {
|
||||
ReservationTaskDetailPanel: {
|
||||
template: '<section />',
|
||||
},
|
||||
})
|
||||
const wrapper = await mountWithPlugins(ReservationOrderDetailView, '/reservation/orders/20001')
|
||||
await vi.dynamicImportSettled()
|
||||
|
||||
expect(wrapper.text()).toContain('Booking Request')
|
||||
expect(wrapper.text()).toContain('guest@example.test')
|
||||
expect(wrapper.text()).toContain('2')
|
||||
expect(wrapper.text()).toContain('前序任务未完成')
|
||||
expect(wrapper.text()).toContain('V4 当前确认快照')
|
||||
expect(wrapper.text()).toContain('QBD_TRAVEL')
|
||||
expect(wrapper.text()).toContain('Q.B.D. TRAVEL GROUP CO., LTD')
|
||||
expect(wrapper.text()).toContain('2026-07-26')
|
||||
expect(wrapper.text()).toContain('2026-07-29')
|
||||
expect(wrapper.text()).toContain('RM1 × 2')
|
||||
expect(wrapper.text()).toContain('下一步处理')
|
||||
expect(wrapper.find('a[href="/reservation/order-tasks/9001"]').text()).toContain('进入处理页')
|
||||
expect(wrapper.find('.primary-link[href="/reservation/order-tasks/9001"]').exists()).toBe(true)
|
||||
expect(wrapper.text()).toContain('关联来源消息')
|
||||
expect(wrapper.text()).toContain('v4@example.test')
|
||||
expect(wrapper.text()).toContain('查看邮件会话')
|
||||
expect(wrapper.find('a[href="/reservation/source-messages/30003/conversation"]').exists()).toBe(true)
|
||||
expect(wrapper.text()).toContain(zhCN.order.v4TaskTimeline)
|
||||
expect(wrapper.text()).toContain('order-ref-1')
|
||||
expect(wrapper.text()).toContain('V4 Booking Package')
|
||||
expect(wrapper.text()).not.toContain('PREVIOUS_TASK_NOT_FINISHED')
|
||||
expect(wrapper.find('.v4-timeline-item__main[href="/reservation/order-tasks/9001"]').exists()).toBe(true)
|
||||
expect(wrapper.text()).toContain('基础信息卡')
|
||||
expect(wrapper.text()).toContain('frontend-query-admin')
|
||||
expect(wrapper.text()).toContain('待复核')
|
||||
expect(wrapper.text()).not.toContain('unsafe ai payload must stay hidden')
|
||||
expect(wrapper.text()).not.toContain('unsafe display payload must stay hidden')
|
||||
expect(wrapper.text()).not.toContain('unsafe confirmed payload must stay hidden')
|
||||
expect(wrapper.text()).not.toContain('unsafe mail body must stay hidden')
|
||||
expect(wrapper.text()).not.toContain('https://oss.example.test')
|
||||
expect(wrapper.findComponent({ name: 'ReservationTaskDetailPanel' }).exists()).toBe(false)
|
||||
expect(wrapper.text()).not.toContain('来源邮件摘要字段待后端补充')
|
||||
})
|
||||
|
||||
it('shows empty states when an order has no V4 overview, next action, source messages or task timeline', async () => {
|
||||
const result = createOrderDetailResult()
|
||||
result.order_overview = null
|
||||
result.next_v4_action = {
|
||||
order_task_id: null,
|
||||
card_id: null,
|
||||
action_type: 'NONE',
|
||||
action_status: null,
|
||||
open_order_task_count: 0,
|
||||
}
|
||||
result.related_source_messages = []
|
||||
result.v4_order_tasks = []
|
||||
vi.mocked(service.fetchReservationOrderDetail).mockResolvedValue(result)
|
||||
|
||||
const wrapper = await mountWithPlugins(ReservationOrderDetailView, '/reservation/orders/20001')
|
||||
await vi.dynamicImportSettled()
|
||||
|
||||
expect(wrapper.text()).toContain('暂无已确认的 V4 订单快照')
|
||||
expect(wrapper.text()).toContain('暂无下一步 V4 处理动作')
|
||||
expect(wrapper.text()).toContain('暂无关联来源消息')
|
||||
expect(wrapper.text()).toContain(zhCN.order.v4TaskTimelineEmpty)
|
||||
expect(wrapper.find('a[href^="/reservation/order-tasks/"]').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('renders order list items returned by the backend', async () => {
|
||||
vi.mocked(service.fetchReservationOrders).mockResolvedValue(createOrderListResult())
|
||||
|
||||
|
||||
@@ -246,6 +246,36 @@ export interface ReservationV4CardCounts {
|
||||
confirmed_count: number
|
||||
}
|
||||
|
||||
export interface ReservationOrderV4RoomSummary {
|
||||
room_type_code: string | null
|
||||
room_count: number | null
|
||||
}
|
||||
|
||||
export interface ReservationOrderV4OverviewResult {
|
||||
account_code: string | null
|
||||
account_name: string | null
|
||||
market_code: string | null
|
||||
source_code: string | null
|
||||
arrival_date: string | null
|
||||
departure_date: string | null
|
||||
rate_code: string | null
|
||||
room_items: ReservationOrderV4RoomSummary[]
|
||||
trace_card_status: ReservationV4CardStatus | string | null
|
||||
rooming_list_card_status: ReservationV4CardStatus | string | null
|
||||
payment_card_status: ReservationV4CardStatus | string | null
|
||||
latest_confirmed_at: string | null
|
||||
}
|
||||
|
||||
export type ReservationOrderV4NextActionType = 'CONFIRM' | 'REVIEW' | 'NONE' | string
|
||||
|
||||
export interface ReservationOrderV4NextActionResult {
|
||||
order_task_id: string | null
|
||||
card_id: string | null
|
||||
action_type: ReservationOrderV4NextActionType | null
|
||||
action_status: ReservationV4CardStatus | string | null
|
||||
open_order_task_count: number | null
|
||||
}
|
||||
|
||||
export interface ReservationV4ActionAvailability {
|
||||
blocked: boolean
|
||||
read_only: boolean
|
||||
@@ -453,6 +483,7 @@ export interface ReservationV4OrderTaskTimelineItem {
|
||||
order_ref: string | null
|
||||
order_task_status: ReservationV4OrderTaskStatus | string
|
||||
card_counts: ReservationV4CardCounts
|
||||
cards: ReservationOrderV4TaskCardTimelineItem[]
|
||||
source_message_summary: ReservationV4SourceMessageSummary | null
|
||||
source_received_at: string | null
|
||||
created_at: string | null
|
||||
@@ -460,8 +491,24 @@ export interface ReservationV4OrderTaskTimelineItem {
|
||||
latest_activity_at: string | null
|
||||
}
|
||||
|
||||
export interface ReservationOrderV4TaskCardTimelineItem {
|
||||
card_id: string
|
||||
card_type: ReservationV4CardType | string
|
||||
event_type: string | null
|
||||
source_event_index: number | null
|
||||
card_sort_order: number | null
|
||||
card_status: ReservationV4CardStatus | string
|
||||
review_status: string | null
|
||||
confirmed_by: string | null
|
||||
confirmed_at: string | null
|
||||
latest_activity_at: string | null
|
||||
}
|
||||
|
||||
export interface ReservationOrderDetailResult {
|
||||
order: ReservationOrderSummaryResult
|
||||
order_overview: ReservationOrderV4OverviewResult | null
|
||||
next_v4_action: ReservationOrderV4NextActionResult | null
|
||||
related_source_messages: ReservationV4SourceMessageSummary[]
|
||||
tasks: ReservationOrderTaskTimelineItem[]
|
||||
v4_order_tasks: ReservationV4OrderTaskTimelineItem[]
|
||||
warnings: string[]
|
||||
|
||||
@@ -51,110 +51,290 @@
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<div class="order-grid">
|
||||
<aside class="order-side">
|
||||
<section class="th-section side-section">
|
||||
<div class="th-section-header">
|
||||
<div class="overview-layout">
|
||||
<section class="th-section overview-section">
|
||||
<div class="th-section-header">
|
||||
<div>
|
||||
<h2 class="th-section-title">
|
||||
{{ t('order.taskQueue') }}
|
||||
{{ t('order.v4OverviewTitle') }}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="side-section__body">
|
||||
<ReservationTaskQueue
|
||||
:items="detail.tasks"
|
||||
:active-task-id="activeTaskId"
|
||||
@open-task="activeTaskId = $event"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="th-section side-section">
|
||||
<div class="th-section-header">
|
||||
<h2 class="th-section-title">
|
||||
{{ t('order.v4TaskTimeline') }}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="side-section__body">
|
||||
<p
|
||||
v-if="!v4OrderTasks.length"
|
||||
class="side-empty"
|
||||
>
|
||||
{{ t('order.v4TaskTimelineEmpty') }}
|
||||
<p class="th-muted">
|
||||
{{ t('order.v4OverviewSubtitle') }}
|
||||
</p>
|
||||
<template v-else>
|
||||
<RouterLink
|
||||
v-for="item in v4OrderTasks"
|
||||
:key="item.order_task_id"
|
||||
class="v4-timeline-item"
|
||||
:to="`/reservation/order-tasks/${item.order_task_id}`"
|
||||
>
|
||||
<span>
|
||||
<strong>{{ v4OrderReference(item) }}</strong>
|
||||
<small>{{ item.source_message_summary?.subject ?? '-' }}</small>
|
||||
</span>
|
||||
<ReservationStatusBadge :status="item.order_task_status" />
|
||||
<small>
|
||||
{{ t('taskList.cardCountsTotal', { total: item.card_counts.total_count }) }}
|
||||
·
|
||||
{{ formatReservationDateTime(item.latest_activity_at ?? item.updated_at ?? item.created_at) }}
|
||||
</small>
|
||||
</RouterLink>
|
||||
</template>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="th-section source-gap">
|
||||
<h2>{{ t('task.evidence') }}</h2>
|
||||
<template v-if="activeTask">
|
||||
<dl>
|
||||
<div>
|
||||
<dt>{{ t('task.sourceSubject') }}</dt>
|
||||
<dd>{{ activeTask.source_subject ?? '-' }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>{{ t('task.sourceSender') }}</dt>
|
||||
<dd>{{ activeTask.source_sender_summary ?? '-' }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>{{ t('task.sourceReceivedAt') }}</dt>
|
||||
<dd>{{ formatReservationDateTime(activeTask.source_received_at) }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>{{ t('order.conversationMessageCount') }}</dt>
|
||||
<dd>{{ activeTask.conversation_message_count ?? '-' }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<RouterLink
|
||||
v-if="activeTask.source_message_id"
|
||||
class="source-link"
|
||||
:to="`/reservation/source-messages/${activeTask.source_message_id}/conversation`"
|
||||
<template v-if="hasOrderOverview">
|
||||
<dl class="overview-facts">
|
||||
<div
|
||||
v-for="fact in overviewFacts"
|
||||
:key="fact.label"
|
||||
>
|
||||
{{ t('task.viewConversation') }}
|
||||
</RouterLink>
|
||||
<span
|
||||
v-else
|
||||
class="source-link source-link--disabled"
|
||||
>
|
||||
{{ t('common.endpointMissing') }}
|
||||
</span>
|
||||
</template>
|
||||
<p v-else>
|
||||
{{ t('common.noData') }}
|
||||
</p>
|
||||
</section>
|
||||
</aside>
|
||||
<dt>{{ fact.label }}</dt>
|
||||
<dd>{{ fact.value }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
|
||||
<ReservationTaskDetailPanel
|
||||
v-if="activeTaskId"
|
||||
:task-id="activeTaskId"
|
||||
/>
|
||||
<section
|
||||
v-else
|
||||
class="th-section order-state"
|
||||
>
|
||||
{{ t('common.noData') }}
|
||||
<div class="room-summary">
|
||||
<span>{{ t('order.roomItems') }}</span>
|
||||
<div
|
||||
v-if="overviewRoomItems.length"
|
||||
class="room-summary__items"
|
||||
>
|
||||
<strong
|
||||
v-for="room in overviewRoomItems"
|
||||
:key="`${room.room_type_code ?? '-'}-${room.room_count ?? '-'}`"
|
||||
>
|
||||
{{ formatRoomSummary(room) }}
|
||||
</strong>
|
||||
</div>
|
||||
<strong v-else>-</strong>
|
||||
</div>
|
||||
|
||||
<div class="overview-statuses">
|
||||
<div
|
||||
v-for="statusItem in overviewStatuses"
|
||||
:key="statusItem.label"
|
||||
>
|
||||
<span>{{ statusItem.label }}</span>
|
||||
<ReservationStatusBadge
|
||||
v-if="statusItem.status"
|
||||
:status="statusItem.status"
|
||||
/>
|
||||
<strong v-else>-</strong>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<p
|
||||
v-else
|
||||
class="empty-copy"
|
||||
>
|
||||
{{ t('order.v4OverviewEmpty') }}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="th-section next-action-section">
|
||||
<div class="th-section-header">
|
||||
<div>
|
||||
<h2 class="th-section-title">
|
||||
{{ t('order.nextV4ActionTitle') }}
|
||||
</h2>
|
||||
<p class="th-muted">
|
||||
{{ t('order.nextV4ActionSubtitle') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="hasNextV4Action && nextV4Action">
|
||||
<dl class="next-action-facts">
|
||||
<div>
|
||||
<dt>{{ t('order.nextV4ActionType') }}</dt>
|
||||
<dd>{{ formatNextActionType(nextV4Action.action_type) }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>{{ t('order.nextV4ActionStatus') }}</dt>
|
||||
<dd>
|
||||
<ReservationStatusBadge
|
||||
v-if="nextV4Action.action_status"
|
||||
:status="nextV4Action.action_status"
|
||||
/>
|
||||
<span v-else>-</span>
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>{{ t('order.openOrderTaskCount') }}</dt>
|
||||
<dd>{{ nextV4Action.open_order_task_count ?? 0 }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>{{ t('order.nextV4CardId') }}</dt>
|
||||
<dd>{{ nextV4Action.card_id ?? '-' }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<RouterLink
|
||||
class="primary-link"
|
||||
:to="`/reservation/order-tasks/${nextV4Action.order_task_id}`"
|
||||
>
|
||||
{{ t('order.enterV4Task') }}
|
||||
</RouterLink>
|
||||
</template>
|
||||
<p
|
||||
v-else
|
||||
class="empty-copy"
|
||||
>
|
||||
{{ t('order.nextV4ActionEmpty') }}
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="th-section source-section">
|
||||
<div class="th-section-header">
|
||||
<div>
|
||||
<h2 class="th-section-title">
|
||||
{{ t('order.relatedSourceMessages') }}
|
||||
</h2>
|
||||
<p class="th-muted">
|
||||
{{ t('order.relatedSourceMessagesHint') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="relatedSourceMessages.length"
|
||||
class="source-list"
|
||||
>
|
||||
<article
|
||||
v-for="sourceMessage in relatedSourceMessages"
|
||||
:key="sourceMessage.source_message_id"
|
||||
class="source-item"
|
||||
>
|
||||
<div>
|
||||
<strong>{{ sourceMessage.subject ?? t('taskList.noSourceSubject') }}</strong>
|
||||
<span>{{ sourceMessage.sender_summary ?? '-' }}</span>
|
||||
</div>
|
||||
<dl>
|
||||
<div>
|
||||
<dt>{{ t('task.sourceReceivedAt') }}</dt>
|
||||
<dd>{{ formatReservationDateTime(sourceMessage.received_at) }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>{{ t('order.conversationMessageCount') }}</dt>
|
||||
<dd>{{ sourceMessage.conversation_message_count ?? '-' }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>{{ t('order.externalConversationId') }}</dt>
|
||||
<dd>{{ sourceMessage.external_conversation_id ?? '-' }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<RouterLink
|
||||
class="source-link"
|
||||
:to="`/reservation/source-messages/${sourceMessage.source_message_id}/conversation`"
|
||||
>
|
||||
{{ t('task.viewConversation') }}
|
||||
</RouterLink>
|
||||
</article>
|
||||
</div>
|
||||
<p
|
||||
v-else
|
||||
class="empty-copy"
|
||||
>
|
||||
{{ t('order.relatedSourceMessagesEmpty') }}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="th-section v4-timeline-section">
|
||||
<div class="th-section-header">
|
||||
<div>
|
||||
<h2 class="th-section-title">
|
||||
{{ t('order.v4TaskTimeline') }}
|
||||
</h2>
|
||||
<p class="th-muted">
|
||||
{{ t('order.v4TaskTimelineHint') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p
|
||||
v-if="!v4OrderTasks.length"
|
||||
class="empty-copy"
|
||||
>
|
||||
{{ t('order.v4TaskTimelineEmpty') }}
|
||||
</p>
|
||||
<div
|
||||
v-else
|
||||
class="v4-timeline-list"
|
||||
>
|
||||
<article
|
||||
v-for="item in v4OrderTasks"
|
||||
:key="item.order_task_id"
|
||||
class="v4-timeline-item"
|
||||
>
|
||||
<RouterLink
|
||||
class="v4-timeline-item__main"
|
||||
:to="`/reservation/order-tasks/${item.order_task_id}`"
|
||||
>
|
||||
<span>
|
||||
<strong>{{ v4OrderReference(item) }}</strong>
|
||||
<small>{{ item.source_message_summary?.subject ?? t('taskList.noSourceSubject') }}</small>
|
||||
</span>
|
||||
<ReservationStatusBadge :status="item.order_task_status" />
|
||||
</RouterLink>
|
||||
|
||||
<dl class="timeline-meta">
|
||||
<div>
|
||||
<dt>{{ t('taskList.cardCountsTotal', { total: item.card_counts.total_count }) }}</dt>
|
||||
<dd>
|
||||
{{ t('taskList.cardCountsPending', { count: item.card_counts.pending_confirm_count }) }}
|
||||
·
|
||||
{{ t('taskList.cardCountsReview', { count: item.card_counts.review_required_count }) }}
|
||||
·
|
||||
{{ t('taskList.cardCountsConfirmed', { count: item.card_counts.confirmed_count }) }}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>{{ t('task.sourceReceivedAt') }}</dt>
|
||||
<dd>{{ formatReservationDateTime(item.source_received_at) }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>{{ t('order.latestActivityAt') }}</dt>
|
||||
<dd>{{ formatReservationDateTime(item.latest_activity_at ?? item.updated_at ?? item.created_at) }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
|
||||
<div
|
||||
v-if="item.cards.length"
|
||||
class="card-summary-list"
|
||||
>
|
||||
<article
|
||||
v-for="card in item.cards"
|
||||
:key="card.card_id"
|
||||
class="card-summary"
|
||||
>
|
||||
<div class="card-summary__heading">
|
||||
<span>
|
||||
<strong>{{ cardTimelineTitle(card) }}</strong>
|
||||
<small>{{ card.event_type ?? '-' }}</small>
|
||||
</span>
|
||||
<ReservationStatusBadge :status="card.card_status" />
|
||||
</div>
|
||||
<dl>
|
||||
<div>
|
||||
<dt>{{ t('order.cardId') }}</dt>
|
||||
<dd>{{ card.card_id }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>{{ t('order.reviewStatus') }}</dt>
|
||||
<dd>
|
||||
<ReservationStatusBadge
|
||||
v-if="card.review_status"
|
||||
:status="card.review_status"
|
||||
/>
|
||||
<span v-else>-</span>
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>{{ t('order.confirmedBy') }}</dt>
|
||||
<dd>{{ card.confirmed_by ?? '-' }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>{{ t('order.confirmedAt') }}</dt>
|
||||
<dd>{{ formatReservationDateTime(card.confirmed_at) }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>{{ t('order.latestActivityAt') }}</dt>
|
||||
<dd>{{ formatReservationDateTime(card.latest_activity_at) }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</article>
|
||||
</div>
|
||||
<p
|
||||
v-else
|
||||
class="empty-copy empty-copy--compact"
|
||||
>
|
||||
{{ t('order.v4CardsEmpty') }}
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
@@ -165,11 +345,16 @@ import { RouterLink, useRoute } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import ReservationStatusBadge from '@/components/reservation/ReservationStatusBadge.vue'
|
||||
import ReservationTaskDetailPanel from '@/components/reservation/ReservationTaskDetailPanel.vue'
|
||||
import ReservationTaskQueue from '@/components/reservation/ReservationTaskQueue.vue'
|
||||
import { fetchReservationOrderDetail } from '@/services/reservationService'
|
||||
import { useAuthStore } from '@/stores/authStore'
|
||||
import type { ReservationOrderDetailResult, ReservationV4OrderTaskTimelineItem } from '@/types/reservation'
|
||||
import type {
|
||||
ReservationOrderDetailResult,
|
||||
ReservationOrderV4NextActionResult,
|
||||
ReservationOrderV4RoomSummary,
|
||||
ReservationOrderV4TaskCardTimelineItem,
|
||||
ReservationV4OrderTaskTimelineItem,
|
||||
} from '@/types/reservation'
|
||||
import { formatReservationTaskCard } from '@/utils/reservationDisplay'
|
||||
import { formatReservationDateTime } from '@/utils/reservationFormat'
|
||||
|
||||
const route = useRoute()
|
||||
@@ -178,11 +363,61 @@ const authStore = useAuthStore()
|
||||
const loading = ref(false)
|
||||
const errorMessage = ref('')
|
||||
const detail = ref<ReservationOrderDetailResult | null>(null)
|
||||
const activeTaskId = ref('')
|
||||
|
||||
const orderId = computed(() => String(route.params.orderId ?? ''))
|
||||
const activeTask = computed(() => detail.value?.tasks.find((task) => task.task_id === activeTaskId.value) ?? null)
|
||||
const orderOverview = computed(() => detail.value?.order_overview ?? null)
|
||||
const nextV4Action = computed(() => detail.value?.next_v4_action ?? null)
|
||||
const relatedSourceMessages = computed(() => detail.value?.related_source_messages ?? [])
|
||||
const v4OrderTasks = computed(() => detail.value?.v4_order_tasks ?? [])
|
||||
const overviewRoomItems = computed(() => orderOverview.value?.room_items ?? [])
|
||||
const hasNextV4Action = computed(() => {
|
||||
const action = nextV4Action.value
|
||||
return Boolean(action?.order_task_id && action.action_type && action.action_type !== 'NONE')
|
||||
})
|
||||
const hasOrderOverview = computed(() => {
|
||||
const overview = orderOverview.value
|
||||
if (!overview) {
|
||||
return false
|
||||
}
|
||||
return Boolean(
|
||||
overview.account_code ||
|
||||
overview.account_name ||
|
||||
overview.market_code ||
|
||||
overview.source_code ||
|
||||
overview.arrival_date ||
|
||||
overview.departure_date ||
|
||||
overview.rate_code ||
|
||||
overview.room_items.length ||
|
||||
overview.trace_card_status ||
|
||||
overview.rooming_list_card_status ||
|
||||
overview.payment_card_status ||
|
||||
overview.latest_confirmed_at,
|
||||
)
|
||||
})
|
||||
const overviewFacts = computed(() => {
|
||||
const overview = orderOverview.value
|
||||
if (!overview) {
|
||||
return []
|
||||
}
|
||||
return [
|
||||
{ label: t('order.accountCode'), value: displayValue(overview.account_code) },
|
||||
{ label: t('order.accountName'), value: displayValue(overview.account_name) },
|
||||
{ label: t('order.marketCode'), value: displayValue(overview.market_code) },
|
||||
{ label: t('order.sourceCode'), value: displayValue(overview.source_code) },
|
||||
{ label: t('order.arrivalDate'), value: displayValue(overview.arrival_date) },
|
||||
{ label: t('order.departureDate'), value: displayValue(overview.departure_date) },
|
||||
{ label: t('order.rateCode'), value: displayValue(overview.rate_code) },
|
||||
{ label: t('order.latestConfirmedAt'), value: formatReservationDateTime(overview.latest_confirmed_at) },
|
||||
]
|
||||
})
|
||||
const overviewStatuses = computed(() => {
|
||||
const overview = orderOverview.value
|
||||
return [
|
||||
{ label: t('order.traceCardStatus'), status: overview?.trace_card_status ?? null },
|
||||
{ label: t('order.roomingListCardStatus'), status: overview?.rooming_list_card_status ?? null },
|
||||
{ label: t('order.paymentCardStatus'), status: overview?.payment_card_status ?? null },
|
||||
]
|
||||
})
|
||||
|
||||
watch(
|
||||
orderId,
|
||||
@@ -206,21 +441,45 @@ async function loadOrder(nextOrderId: string): Promise<void> {
|
||||
loading.value = true
|
||||
errorMessage.value = ''
|
||||
try {
|
||||
const nextOrder = await fetchReservationOrderDetail(nextOrderId)
|
||||
detail.value = nextOrder
|
||||
activeTaskId.value = nextOrder.tasks[0]?.task_id ?? ''
|
||||
detail.value = await fetchReservationOrderDetail(nextOrderId)
|
||||
} catch (error) {
|
||||
detail.value = null
|
||||
activeTaskId.value = ''
|
||||
errorMessage.value = error instanceof Error ? error.message : String(error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function displayValue(value: string | null | undefined): string {
|
||||
return value?.trim() || '-'
|
||||
}
|
||||
|
||||
function formatRoomSummary(room: ReservationOrderV4RoomSummary): string {
|
||||
return `${displayValue(room.room_type_code)} × ${room.room_count ?? '-'}`
|
||||
}
|
||||
|
||||
function formatNextActionType(actionType: ReservationOrderV4NextActionResult['action_type']): string {
|
||||
if (actionType === 'CONFIRM') {
|
||||
return t('order.nextActionConfirm')
|
||||
}
|
||||
if (actionType === 'REVIEW') {
|
||||
return t('order.nextActionReview')
|
||||
}
|
||||
if (actionType === 'NONE') {
|
||||
return t('order.nextActionNone')
|
||||
}
|
||||
return actionType ?? '-'
|
||||
}
|
||||
|
||||
function v4OrderReference(item: ReservationV4OrderTaskTimelineItem): string {
|
||||
return item.order_ref?.trim() || item.source_message_summary?.subject?.trim() || `#${item.order_task_id}`
|
||||
}
|
||||
|
||||
function cardTimelineTitle(card: ReservationOrderV4TaskCardTimelineItem): string {
|
||||
const title = formatReservationTaskCard(t, card.card_type, card.card_type)
|
||||
const index = card.source_event_index == null ? '' : ` · ${t('order.sourceEventIndex', { index: card.source_event_index })}`
|
||||
return `${title}${index}`
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -240,7 +499,11 @@ function v4OrderReference(item: ReservationV4OrderTaskTimelineItem): string {
|
||||
color: var(--th-color-danger);
|
||||
}
|
||||
|
||||
.order-summary {
|
||||
.order-summary,
|
||||
.overview-section,
|
||||
.next-action-section,
|
||||
.source-section,
|
||||
.v4-timeline-section {
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
padding: 20px;
|
||||
@@ -255,7 +518,9 @@ function v4OrderReference(item: ReservationV4OrderTaskTimelineItem): string {
|
||||
|
||||
.order-summary p,
|
||||
.order-summary h1,
|
||||
.order-summary dl {
|
||||
.order-summary dl,
|
||||
.empty-copy,
|
||||
.th-muted {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@@ -271,19 +536,48 @@ function v4OrderReference(item: ReservationV4OrderTaskTimelineItem): string {
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.order-summary dl {
|
||||
.order-summary dl,
|
||||
.overview-facts,
|
||||
.next-action-facts,
|
||||
.source-item dl,
|
||||
.timeline-meta,
|
||||
.card-summary dl {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, minmax(0, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.order-summary dt {
|
||||
.order-summary dl,
|
||||
.overview-facts {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.next-action-facts,
|
||||
.source-item dl,
|
||||
.timeline-meta {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.card-summary dl {
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.order-summary dt,
|
||||
.overview-facts dt,
|
||||
.next-action-facts dt,
|
||||
.source-item dt,
|
||||
.timeline-meta dt,
|
||||
.card-summary dt {
|
||||
color: var(--th-color-slate-500);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.order-summary dd {
|
||||
.order-summary dd,
|
||||
.overview-facts dd,
|
||||
.next-action-facts dd,
|
||||
.source-item dd,
|
||||
.timeline-meta dd,
|
||||
.card-summary dd {
|
||||
margin: 5px 0 0;
|
||||
color: var(--th-color-slate-900);
|
||||
font-size: 13px;
|
||||
@@ -291,111 +585,61 @@ function v4OrderReference(item: ReservationV4OrderTaskTimelineItem): string {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.order-grid {
|
||||
.overview-layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(260px, 340px) minmax(0, 1fr);
|
||||
grid-template-columns: minmax(0, 2fr) minmax(300px, 0.9fr);
|
||||
gap: 18px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.order-side,
|
||||
.side-section__body {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.side-section {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
.side-section__body {
|
||||
padding: 14px 16px 0;
|
||||
}
|
||||
|
||||
.source-gap {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.source-gap h2,
|
||||
.source-gap p,
|
||||
.source-gap dl {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.source-gap h2 {
|
||||
color: var(--th-color-slate-900);
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.source-gap p {
|
||||
.th-muted,
|
||||
.empty-copy {
|
||||
color: var(--th-color-slate-500);
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.side-empty {
|
||||
margin: 0;
|
||||
color: var(--th-color-slate-500);
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
.empty-copy--compact {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.v4-timeline-item {
|
||||
.room-summary,
|
||||
.overview-statuses {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: 8px;
|
||||
border: 1px solid var(--th-color-slate-200);
|
||||
border-radius: var(--th-radius-sm);
|
||||
background: var(--th-color-white);
|
||||
color: inherit;
|
||||
padding: 10px;
|
||||
text-decoration: none;
|
||||
border-top: 1px solid var(--th-color-slate-100);
|
||||
padding-top: 14px;
|
||||
}
|
||||
|
||||
.v4-timeline-item > span {
|
||||
display: grid;
|
||||
gap: 3px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.v4-timeline-item strong {
|
||||
color: var(--th-color-slate-900);
|
||||
font-size: 13px;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.v4-timeline-item small {
|
||||
.room-summary > span,
|
||||
.overview-statuses span {
|
||||
color: var(--th-color-slate-500);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.v4-timeline-item > small {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.source-gap dl {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.source-gap dt {
|
||||
color: var(--th-color-slate-500);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.source-gap dd {
|
||||
margin: 4px 0 0;
|
||||
color: var(--th-color-slate-900);
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.room-summary__items,
|
||||
.overview-statuses {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.room-summary__items strong {
|
||||
border-radius: 999px;
|
||||
background: var(--th-color-slate-100);
|
||||
color: var(--th-color-slate-900);
|
||||
padding: 5px 10px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.overview-statuses div {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.primary-link,
|
||||
.source-link {
|
||||
color: var(--th-color-blue-600);
|
||||
font-size: 13px;
|
||||
@@ -403,23 +647,119 @@ function v4OrderReference(item: ReservationV4OrderTaskTimelineItem): string {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.source-link--disabled {
|
||||
.primary-link {
|
||||
width: fit-content;
|
||||
border-radius: var(--th-radius-sm);
|
||||
background: var(--th-color-blue-600);
|
||||
color: var(--th-color-white);
|
||||
padding: 9px 12px;
|
||||
}
|
||||
|
||||
.source-list,
|
||||
.v4-timeline-list,
|
||||
.card-summary-list {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.source-item,
|
||||
.v4-timeline-item,
|
||||
.card-summary {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
border: 1px solid var(--th-color-slate-200);
|
||||
border-radius: var(--th-radius-sm);
|
||||
background: var(--th-color-white);
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.source-item > div,
|
||||
.v4-timeline-item__main,
|
||||
.card-summary__heading {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.source-item > div {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.source-item > div > strong,
|
||||
.source-item > div > span,
|
||||
.v4-timeline-item__main span,
|
||||
.card-summary__heading span {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.source-item > div > strong,
|
||||
.v4-timeline-item__main strong,
|
||||
.card-summary__heading strong {
|
||||
color: var(--th-color-slate-900);
|
||||
font-size: 14px;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.source-item > div > span,
|
||||
.v4-timeline-item__main small,
|
||||
.card-summary__heading small {
|
||||
color: var(--th-color-slate-500);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.v4-timeline-item__main {
|
||||
align-items: center;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.v4-timeline-item__main span,
|
||||
.card-summary__heading span {
|
||||
display: grid;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.card-summary-list {
|
||||
border-top: 1px solid var(--th-color-slate-100);
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
.card-summary {
|
||||
background: var(--th-color-slate-50);
|
||||
}
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
.order-summary dl {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
.overview-layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.order-grid {
|
||||
grid-template-columns: 1fr;
|
||||
.order-summary dl,
|
||||
.overview-facts,
|
||||
.next-action-facts,
|
||||
.source-item dl,
|
||||
.timeline-meta,
|
||||
.card-summary dl {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 680px) {
|
||||
.order-summary dl {
|
||||
.order-summary dl,
|
||||
.overview-facts,
|
||||
.next-action-facts,
|
||||
.source-item dl,
|
||||
.timeline-meta,
|
||||
.card-summary dl {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.source-item > div,
|
||||
.v4-timeline-item__main,
|
||||
.card-summary__heading {
|
||||
display: grid;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user