优化我的订单页

This commit is contained in:
yml2213
2026-07-28 11:06:17 +08:00
parent 2ab80c4df8
commit cdfee71c97
2 changed files with 681 additions and 84 deletions
@@ -1,12 +1,20 @@
import { CheckOutlined, ReloadOutlined } from '@ant-design/icons' import {
CheckOutlined,
EyeOutlined,
ReloadOutlined,
SearchOutlined,
} from '@ant-design/icons'
import { useQuery, useQueryClient } from '@tanstack/react-query' import { useQuery, useQueryClient } from '@tanstack/react-query'
import { import {
App, App,
Button, Button,
Card,
Descriptions,
Drawer,
Empty,
Form, Form,
Input, Input,
Modal, Modal,
Select,
Space, Space,
Table, Table,
Tag, Tag,
@@ -18,35 +26,116 @@ import { useState } from 'react'
import ImagePreviewList from '@/components/files/ImagePreviewList' import ImagePreviewList from '@/components/files/ImagePreviewList'
import ImageUpload from '@/components/files/ImageUpload' import ImageUpload from '@/components/files/ImageUpload'
import { fetchWorkerMyOrders, submitWorkerAcceptance } from '@/services/worker' import { fetchWorkerMyOrders, submitWorkerAcceptance } from '@/services/worker'
import type { UploadedFile, WorkOrder } from '@/types/worker-platform' import type { CollectField, UploadedFile, WorkOrder } from '@/types/worker-platform'
import { formatDateTime } from '@/utils/date-time'
const STATUS_OPTIONS = [ const STATUS_OPTIONS = [
{ value: '', label: '全部' }, { value: '', label: '全部订单' },
{ value: 'in_progress', label: '代练中' }, { value: 'in_progress', label: '代练中' },
{ value: 'pending_acceptance', label: '待验收' }, { value: 'pending_acceptance', label: '待验收' },
{ value: 'problem', label: '问题单' }, { value: 'problem', label: '问题单' },
{ value: 'accepted', label: '已验收' }, { value: 'accepted', label: '已验收' },
{ value: 'cancelled', label: '已取消' }, { value: 'cancelled', label: '已取消' },
] ] as const
const STATUS_OVERVIEW = [
{
key: 'in_progress',
label: '代练中',
note: '正在执行中的订单',
tone: 'blue',
},
{
key: 'pending_acceptance',
label: '待验收',
note: '已经提交,等待审核',
tone: 'gold',
},
{
key: 'problem',
label: '问题单',
note: '需要补充或重新处理',
tone: 'red',
},
{
key: 'accepted',
label: '已验收',
note: '已完成并结算的订单',
tone: 'green',
},
{
key: 'cancelled',
label: '已取消',
note: '已结束且不再继续',
tone: 'slate',
},
] as const
type AcceptanceFormValues = {
note?: string
}
type DetailFieldItem = {
key: string
label: string
required: boolean
value: string
}
export default function WorkerOrdersPage() { export default function WorkerOrdersPage() {
const { message } = App.useApp() const { message } = App.useApp()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const [status, setStatus] = useState('') const [status, setStatus] = useState('')
const [keywordInput, setKeywordInput] = useState('')
const [keyword, setKeyword] = useState('')
const [page, setPage] = useState(1) const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(20) const [pageSize, setPageSize] = useState(20)
const [detailOrder, setDetailOrder] = useState<WorkOrder | null>(null)
const [submittingOrder, setSubmittingOrder] = useState<WorkOrder | null>(null) const [submittingOrder, setSubmittingOrder] = useState<WorkOrder | null>(null)
const [acceptanceFiles, setAcceptanceFiles] = useState<UploadedFile[]>([]) const [acceptanceFiles, setAcceptanceFiles] = useState<UploadedFile[]>([])
const [form] = Form.useForm() const [form] = Form.useForm<AcceptanceFormValues>()
const ordersQuery = useQuery({ const ordersQuery = useQuery({
queryKey: ['worker-my-orders', status, page, pageSize], queryKey: ['worker-my-orders', status, keyword, page, pageSize],
queryFn: () => fetchWorkerMyOrders({ status, page, pageSize }), queryFn: () => fetchWorkerMyOrders({ status, keyword, page, pageSize }),
retry: false, retry: false,
}) })
const pagination = ordersQuery.data?.data.pagination const pagination = ordersQuery.data?.data.pagination
const orders = ordersQuery.data?.data.items || []
async function submitAcceptance(values: { note?: string }) { const overviewQuery = useQuery({
queryKey: ['worker-my-orders-overview'],
queryFn: async () => {
const results = await Promise.all(
STATUS_OPTIONS.map(async (item) => {
const response = await fetchWorkerMyOrders({
status: item.value,
page: 1,
pageSize: 1,
})
return [item.value || 'all', response.data.pagination.total] as const
}),
)
return Object.fromEntries(results) as Record<string, number>
},
retry: false,
})
async function refreshAll() {
await Promise.all([
queryClient.invalidateQueries({ queryKey: ['worker-my-orders'] }),
queryClient.invalidateQueries({ queryKey: ['worker-my-orders-overview'] }),
])
}
function openAcceptanceModal(order: WorkOrder) {
setSubmittingOrder(order)
setAcceptanceFiles([])
form.resetFields()
setDetailOrder(null)
}
async function submitAcceptance(values: AcceptanceFormValues) {
if (!submittingOrder) return if (!submittingOrder) return
if (acceptanceFiles.length === 0) { if (acceptanceFiles.length === 0) {
message.error('请上传验收图片') message.error('请上传验收图片')
@@ -61,76 +150,110 @@ export default function WorkerOrdersPage() {
setSubmittingOrder(null) setSubmittingOrder(null)
setAcceptanceFiles([]) setAcceptanceFiles([])
form.resetFields() form.resetFields()
await queryClient.invalidateQueries({ queryKey: ['worker-my-orders'] }) await refreshAll()
} catch (error) { } catch (error) {
message.error(error instanceof Error ? error.message : '提交失败') message.error(error instanceof Error ? error.message : '提交失败')
} }
} }
function applyKeywordSearch() {
setKeyword(keywordInput.trim())
setPage(1)
}
const columns: TableColumnsType<WorkOrder> = [ const columns: TableColumnsType<WorkOrder> = [
{ {
title: '订单', title: '订单信息',
minWidth: 280, minWidth: 320,
render: (_, row) => ( render: (_, row) => (
<div className="cell-stack"> <div className="cell-stack">
<Typography.Text strong>{row.productName}</Typography.Text> <Typography.Link onClick={() => setDetailOrder(row)}>
{row.productName || row.workOrderNo}
</Typography.Link>
<Space size={[6, 6]} wrap>
{row.categoryName ? <Tag>{row.categoryName}</Tag> : null}
<Typography.Text type="secondary">
{row.workOrderNo}
</Typography.Text>
</Space>
<Typography.Text type="secondary"> <Typography.Text type="secondary">
{row.platformOrderId} {row.platformOrderId || '-'}
</Typography.Text>
<Typography.Text type="secondary">
{formatDateTime(row.assignedAt)}
</Typography.Text> </Typography.Text>
</div> </div>
), ),
}, },
{ {
title: '金', title: '奖励 / 押金',
width: 130, width: 150,
render: (_, row) => ( render: (_, row) => (
<Typography.Text strong> <div className="cell-stack">
{formatMoney(row.rewardAmount)} <Typography.Text strong>{formatMoney(row.rewardAmount)}</Typography.Text>
</Typography.Text> <Typography.Text type="secondary">
{formatMoney(row.freezeDepositAmount)}
</Typography.Text>
</div>
), ),
}, },
{ {
title: '状态', title: '当前状态',
width: 140, width: 180,
render: (_, row) => ( render: (_, row) => (
<Tag color={resolveStatusColor(row.status)}> <div className="cell-stack">
{formatStatus(row.status)} <Tag color={resolveStatusColor(row.status)}>{formatStatus(row.status)}</Tag>
</Tag> <Typography.Text type="secondary">
{getStatusHint(row.status)}
</Typography.Text>
</div>
), ),
}, },
{ {
title: '验收图片', title: '验收信息',
width: 170, minWidth: 210,
render: (_, row) => ( render: (_, row) => (
<ImagePreviewList <div className="cell-stack">
files={getAcceptanceFiles(row)} <ImagePreviewList
imageUrls={getAcceptanceImageUrls(row)} files={getAcceptanceFiles(row)}
size={46} imageUrls={getAcceptanceImageUrls(row)}
/> size={46}
/>
<Typography.Text type="secondary">
{formatDateTime(getAcceptanceSubmittedAt(row))}
</Typography.Text>
{row.acceptance?.note ? (
<Typography.Text ellipsis>{row.acceptance.note}</Typography.Text>
) : (
<Typography.Text type="secondary"></Typography.Text>
)}
</div>
), ),
}, },
{ {
title: '问题备注', title: '问题备注',
dataIndex: 'problemNote',
minWidth: 180, minWidth: 180,
render: (value) => String(value || '-'), render: (_, row) => (
<Typography.Text ellipsis>
{String(row.problemNote || '').trim() || '-'}
</Typography.Text>
),
}, },
{ {
title: '操作', title: '操作',
width: 140, width: 200,
render: (_, row) => render: (_, row) => (
['in_progress', 'problem'].includes(row.status) ? ( <Space wrap>
<Button <Button icon={<EyeOutlined />} onClick={() => setDetailOrder(row)}>
icon={<CheckOutlined />}
onClick={() => {
setSubmittingOrder(row)
setAcceptanceFiles([])
form.resetFields()
}}
>
</Button> </Button>
) : null, {canSubmitAcceptance(row) ? (
<Button icon={<CheckOutlined />} type="primary" onClick={() => openAcceptanceModal(row)}>
</Button>
) : null}
</Space>
),
}, },
] ]
@@ -138,45 +261,250 @@ export default function WorkerOrdersPage() {
<section className="page-stack"> <section className="page-stack">
<div className="worker-page-head"> <div className="worker-page-head">
<Typography.Title level={3}></Typography.Title> <Typography.Title level={3}></Typography.Title>
<Space> <Button
<Select icon={<ReloadOutlined />}
value={status} loading={ordersQuery.isFetching || overviewQuery.isFetching}
options={STATUS_OPTIONS} onClick={() => refreshAll()}
style={{ width: 140 }} >
onChange={(nextStatus) => {
setStatus(nextStatus) </Button>
setPage(1)
}}
/>
<Button
icon={<ReloadOutlined />}
loading={ordersQuery.isFetching}
onClick={() => ordersQuery.refetch()}
>
</Button>
</Space>
</div> </div>
<Table<WorkOrder> <div className="worker-orders-overview-grid">
rowKey="workOrderId" <Card className="worker-orders-overview-card total" bordered={false}>
loading={ordersQuery.isLoading} <span className="worker-orders-overview-label"></span>
dataSource={ordersQuery.data?.data.items || []} <strong className="worker-orders-overview-value">
columns={columns} {Number(overviewQuery.data?.all || 0)}
pagination={{ </strong>
current: pagination?.page || page, <span className="worker-orders-overview-note"></span>
pageSize: pagination?.pageSize || pageSize, </Card>
total: pagination?.total || 0, {STATUS_OVERVIEW.map((item) => (
showSizeChanger: true, <Card
pageSizeOptions: [10, 20, 50, 100], key={item.key}
showTotal: (total) => `${total}`, className={`worker-orders-overview-card tone-${item.tone}`}
onChange: (nextPage, nextPageSize) => { bordered={false}
setPage(nextPage) >
setPageSize(nextPageSize) <span className="worker-orders-overview-label">{item.label}</span>
}, <strong className="worker-orders-overview-value">
}} {Number(overviewQuery.data?.[item.key] || 0)}
scroll={{ x: 900 }} </strong>
/> <span className="worker-orders-overview-note">{item.note}</span>
</Card>
))}
</div>
<Card bordered={false} className="worker-orders-table-card">
<div className="worker-orders-toolbar">
<Space.Compact className="worker-orders-search">
<Input
allowClear
value={keywordInput}
placeholder="搜索工单号 / 订单号 / 商品名"
prefix={<SearchOutlined />}
onChange={(event) => {
const nextValue = event.target.value
setKeywordInput(nextValue)
if (!nextValue.trim()) {
setKeyword('')
setPage(1)
}
}}
onPressEnter={applyKeywordSearch}
/>
<Button
icon={<SearchOutlined />}
loading={ordersQuery.isFetching}
onClick={applyKeywordSearch}
>
</Button>
</Space.Compact>
<Typography.Text type="secondary">
{STATUS_OPTIONS.find((item) => item.value === status)?.label || '全部订单'}
</Typography.Text>
</div>
<div className="worker-orders-status-tabs">
{STATUS_OPTIONS.map((item) => {
const active = status === item.value
const countKey = item.value || 'all'
return (
<button
key={item.value || 'all'}
type="button"
className={`worker-orders-status-tab${active ? ' is-active' : ''}`}
onClick={() => {
setStatus(item.value)
setPage(1)
}}
>
<span>{item.label}</span>
<strong>{Number(overviewQuery.data?.[countKey] || 0)}</strong>
</button>
)
})}
</div>
<Table<WorkOrder>
rowKey="workOrderId"
loading={ordersQuery.isLoading}
dataSource={orders}
columns={columns}
locale={{
emptyText: ordersQuery.error ? (
<Empty
description={
ordersQuery.error instanceof Error
? ordersQuery.error.message
: '读取我的订单失败'
}
/>
) : (
<Empty
description={
keyword
? `没有找到与 “${keyword}” 相关的订单`
: '当前筛选下暂无订单'
}
/>
),
}}
pagination={{
current: pagination?.page || page,
pageSize: pagination?.pageSize || pageSize,
total: pagination?.total || 0,
showSizeChanger: true,
pageSizeOptions: [10, 20, 50, 100],
showTotal: (total) => `${total}`,
onChange: (nextPage, nextPageSize) => {
setPage(nextPage)
setPageSize(nextPageSize)
},
}}
scroll={{ x: 1160 }}
/>
</Card>
<Drawer
title={
detailOrder
? `${detailOrder.productName || '订单详情'} · ${detailOrder.workOrderNo}`
: '订单详情'
}
width={820}
open={Boolean(detailOrder)}
destroyOnHidden
onClose={() => setDetailOrder(null)}
extra={
detailOrder ? (
<Space>
<Tag color={resolveStatusColor(detailOrder.status)}>
{formatStatus(detailOrder.status)}
</Tag>
{canSubmitAcceptance(detailOrder) ? (
<Button type="primary" icon={<CheckOutlined />} onClick={() => openAcceptanceModal(detailOrder)}>
</Button>
) : null}
</Space>
) : null
}
>
{detailOrder ? (
<div className="worker-order-detail-stack">
<Card title="基础信息" size="small">
<Descriptions column={{ xs: 1, md: 2 }} bordered size="small">
<Descriptions.Item label="商品">
{detailOrder.productName || '-'}
</Descriptions.Item>
<Descriptions.Item label="分类">
{detailOrder.categoryName || '-'}
</Descriptions.Item>
<Descriptions.Item label="工单号">
{detailOrder.workOrderNo}
</Descriptions.Item>
<Descriptions.Item label="平台订单号">
{detailOrder.platformOrderId || '-'}
</Descriptions.Item>
<Descriptions.Item label="状态">
<Tag color={resolveStatusColor(detailOrder.status)}>
{formatStatus(detailOrder.status)}
</Tag>
</Descriptions.Item>
<Descriptions.Item label="阶段说明">
{getStatusHint(detailOrder.status)}
</Descriptions.Item>
<Descriptions.Item label="接单奖励">
{formatMoney(detailOrder.rewardAmount)}
</Descriptions.Item>
<Descriptions.Item label="冻结押金">
{formatMoney(detailOrder.freezeDepositAmount)}
</Descriptions.Item>
<Descriptions.Item label="接单时间">
{formatDateTime(detailOrder.assignedAt)}
</Descriptions.Item>
<Descriptions.Item label="提交验收">
{formatDateTime(getAcceptanceSubmittedAt(detailOrder))}
</Descriptions.Item>
<Descriptions.Item label="验收完成">
{formatDateTime(detailOrder.acceptedAt)}
</Descriptions.Item>
<Descriptions.Item label="更新时间">
{formatDateTime(detailOrder.updatedAt)}
</Descriptions.Item>
</Descriptions>
</Card>
<Card title="资料信息" size="small">
{renderDetailFieldValues(
getMaterialDetailItems(detailOrder),
'当前还没有资料内容',
)}
</Card>
<Card title="资料要求" size="small">
{renderRequirementFieldValues(detailOrder)}
</Card>
<Card title="验收信息" size="small">
<div className="worker-order-detail-stack">
<Descriptions column={{ xs: 1, md: 2 }} bordered size="small">
<Descriptions.Item label="验收图片数量">
{getAcceptanceFiles(detailOrder).length +
getAcceptanceImageUrls(detailOrder).length || 0}
</Descriptions.Item>
<Descriptions.Item label="提交时间">
{formatDateTime(getAcceptanceSubmittedAt(detailOrder))}
</Descriptions.Item>
<Descriptions.Item label="验收说明" span={2}>
<span className="worker-order-detail-value">
{String(detailOrder.acceptance?.note || '').trim() || '-'}
</span>
</Descriptions.Item>
</Descriptions>
{getAcceptanceFiles(detailOrder).length ||
getAcceptanceImageUrls(detailOrder).length ? (
<ImagePreviewList
files={getAcceptanceFiles(detailOrder)}
imageUrls={getAcceptanceImageUrls(detailOrder)}
size={76}
/>
) : (
<Typography.Text type="secondary"></Typography.Text>
)}
</div>
</Card>
{String(detailOrder.problemNote || '').trim() ? (
<Card title="问题备注" size="small">
<span className="worker-order-detail-value">
{String(detailOrder.problemNote || '').trim()}
</span>
</Card>
) : null}
</div>
) : null}
</Drawer>
<Modal <Modal
title="提交验收" title="提交验收"
@@ -191,7 +519,10 @@ export default function WorkerOrdersPage() {
> >
<Form form={form} layout="vertical" onFinish={submitAcceptance}> <Form form={form} layout="vertical" onFinish={submitAcceptance}>
<Form.Item label="完成说明" name="note"> <Form.Item label="完成说明" name="note">
<Input.TextArea rows={3} /> <Input.TextArea
rows={3}
placeholder="可补充代练结果、注意事项或截图说明。"
/>
</Form.Item> </Form.Item>
<Form.Item label="验收图片" required> <Form.Item label="验收图片" required>
<ImageUpload <ImageUpload
@@ -208,6 +539,113 @@ export default function WorkerOrdersPage() {
) )
} }
function canSubmitAcceptance(order: WorkOrder) {
return ['in_progress', 'problem'].includes(order.status)
}
function getRequirementFields(order: WorkOrder | null): CollectField[] {
if (!order) return []
const rawFields = Array.isArray(order.requirement?.fields)
? order.requirement.fields
: []
return rawFields
.map((item) => {
const source = asRecord(item)
const key = String(source.key || '').trim()
if (!key) return null
return {
key,
label: String(source.label || key).trim(),
required: source.required !== false,
}
})
.filter((item): item is CollectField => Boolean(item))
}
function getMaterialFields(order: WorkOrder): Record<string, string> {
const material = asRecord(order.material)
const collect = asRecord(material.collect)
const fields = asRecord(collect.fields || material.fields)
return Object.fromEntries(
Object.entries(fields).map(([key, value]) => [key, String(value || '')]),
)
}
function getMaterialDetailItems(order: WorkOrder): DetailFieldItem[] {
const values = getMaterialFields(order)
const requiredFields = getRequirementFields(order)
const usedKeys = new Set<string>()
const items = requiredFields.map((field) => {
usedKeys.add(field.key)
return {
key: field.key,
label: field.label,
required: field.required,
value: String(values[field.key] || ''),
}
})
Object.entries(values).forEach(([key, value]) => {
if (usedKeys.has(key)) return
items.push({
key,
label: key,
required: false,
value: String(value || ''),
})
})
return items
}
function renderDetailFieldValues(items: DetailFieldItem[], emptyText: string) {
if (items.length === 0) {
return <Typography.Text type="secondary">{emptyText}</Typography.Text>
}
return (
<Descriptions column={1} bordered size="small">
{items.map((item) => (
<Descriptions.Item
key={item.key}
label={
<Space size={6} wrap>
<span>{item.label}</span>
{item.required ? <Tag color="orange"></Tag> : null}
</Space>
}
>
<span className="worker-order-detail-value">
{String(item.value || '').trim() || '-'}
</span>
</Descriptions.Item>
))}
</Descriptions>
)
}
function renderRequirementFieldValues(order: WorkOrder) {
const fields = getRequirementFields(order)
if (fields.length === 0) {
return <Typography.Text type="secondary"></Typography.Text>
}
return (
<Descriptions column={1} bordered size="small">
{fields.map((field) => (
<Descriptions.Item key={field.key} label={field.label}>
<Space size={6} wrap>
<Typography.Text code>{field.key}</Typography.Text>
<Tag color={field.required ? 'orange' : 'default'}>
{field.required ? '必填' : '选填'}
</Tag>
</Space>
</Descriptions.Item>
))}
</Descriptions>
)
}
function getAcceptanceFiles(row: WorkOrder): UploadedFile[] { function getAcceptanceFiles(row: WorkOrder): UploadedFile[] {
return Array.isArray(row.acceptance?.files) ? row.acceptance.files : [] return Array.isArray(row.acceptance?.files) ? row.acceptance.files : []
} }
@@ -220,6 +658,20 @@ function getAcceptanceImageUrls(row: WorkOrder): string[] {
: [] : []
} }
function getAcceptanceSubmittedAt(order: WorkOrder): string | null {
const acceptanceSubmittedAt =
typeof order.acceptance?.submittedAt === 'string'
? order.acceptance.submittedAt
: null
return order.submittedAt || acceptanceSubmittedAt || null
}
function asRecord(value: unknown): Record<string, unknown> {
return value && typeof value === 'object' && !Array.isArray(value)
? (value as Record<string, unknown>)
: {}
}
function formatMoney(value: number | undefined) { function formatMoney(value: number | undefined) {
return `${((Number(value || 0) || 0) / 100).toFixed(2)}` return `${((Number(value || 0) || 0) / 100).toFixed(2)}`
} }
@@ -235,6 +687,17 @@ function formatStatus(status: string) {
return labels[status] || status return labels[status] || status
} }
function getStatusHint(status: string) {
const hints: Record<string, string> = {
in_progress: '订单进行中,完成后请及时提交验收资料。',
pending_acceptance: '已提交验收,等待后台审核。',
problem: '后台已标记问题,请根据备注调整后重新提交。',
accepted: '订单已完成验收,可以在个人中心查看结算记录。',
cancelled: '订单已取消,本次流程已结束。',
}
return hints[status] || '请关注当前订单状态变化。'
}
function resolveStatusColor(status: string) { function resolveStatusColor(status: string) {
if (status === 'accepted') return 'green' if (status === 'accepted') return 'green'
if (status === 'cancelled') return 'default' if (status === 'cancelled') return 'default'
+134
View File
@@ -1688,6 +1688,127 @@ select {
color: #f97316; color: #f97316;
} }
.worker-orders-overview-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 16px;
}
.worker-orders-overview-card {
border: 1px solid #e2e8f0;
box-shadow: 0 14px 32px rgba(15, 23, 42, 0.04);
}
.worker-orders-overview-card .ant-card-body {
display: grid;
gap: 8px;
}
.worker-orders-overview-card.total {
background:
linear-gradient(135deg, rgba(20, 50, 74, 0.96), rgba(27, 93, 115, 0.92)),
#14324a;
}
.worker-orders-overview-card.total .worker-orders-overview-label,
.worker-orders-overview-card.total .worker-orders-overview-value,
.worker-orders-overview-card.total .worker-orders-overview-note {
color: #f8fafc;
}
.worker-orders-overview-card.tone-blue {
border-top: 3px solid #2563eb;
}
.worker-orders-overview-card.tone-gold {
border-top: 3px solid #d97706;
}
.worker-orders-overview-card.tone-red {
border-top: 3px solid #dc2626;
}
.worker-orders-overview-card.tone-green {
border-top: 3px solid #059669;
}
.worker-orders-overview-card.tone-slate {
border-top: 3px solid #64748b;
}
.worker-orders-overview-label {
color: #64748b;
font-size: 13px;
}
.worker-orders-overview-value {
color: #0f172a;
font-size: 28px;
line-height: 1.1;
}
.worker-orders-overview-note {
color: #94a3b8;
font-size: 12px;
line-height: 1.5;
}
.worker-orders-table-card .ant-card-body {
display: grid;
gap: 18px;
}
.worker-orders-toolbar {
display: flex;
gap: 16px;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
}
.worker-orders-search {
width: min(520px, 100%);
}
.worker-orders-status-tabs {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.worker-orders-status-tab {
padding: 10px 14px;
border-radius: 14px;
border: 1px solid #dbe4ee;
background: #f8fafc;
color: #475569;
display: inline-flex;
align-items: center;
gap: 10px;
cursor: pointer;
transition:
border-color 0.18s ease,
background 0.18s ease,
color 0.18s ease,
transform 0.18s ease;
}
.worker-orders-status-tab:hover {
transform: translateY(-1px);
border-color: #90b7cf;
}
.worker-orders-status-tab strong {
font-size: 13px;
}
.worker-orders-status-tab.is-active {
border-color: transparent;
background: linear-gradient(135deg, #1b5d73, #237b77);
color: #f8fafc;
box-shadow: 0 10px 24px rgba(27, 93, 115, 0.24);
}
.worker-profile-hero { .worker-profile-hero {
overflow: hidden; overflow: hidden;
border: 1px solid rgba(25, 94, 125, 0.14); border: 1px solid rgba(25, 94, 125, 0.14);
@@ -1962,6 +2083,19 @@ select {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.worker-orders-toolbar {
align-items: stretch;
}
.worker-orders-search {
width: 100%;
}
.worker-orders-status-tab {
flex: 1 1 calc(50% - 10px);
justify-content: space-between;
}
.worker-profile-hero-head { .worker-profile-hero-head {
flex-direction: column; flex-direction: column;
} }