Files
rmser/rmser-view/src/pages/DraftsList.tsx

315 lines
9.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// src/pages/DraftsList.tsx
import React, { useState } from "react";
import { useQuery } from "@tanstack/react-query";
import {
List,
Typography,
Tag,
Spin,
Empty,
DatePicker,
Flex,
Button,
} from "antd";
import { useNavigate } from "react-router-dom";
import {
ArrowRightOutlined,
CheckCircleOutlined,
DeleteOutlined,
PlusOutlined,
ExclamationCircleOutlined,
LoadingOutlined,
CloseCircleOutlined,
StopOutlined,
SyncOutlined,
CloudServerOutlined,
} from "@ant-design/icons";
import dayjs, { Dayjs } from "dayjs";
import { api } from "../services/api";
import type { UnifiedInvoice } from "../services/types";
const { Title, Text } = Typography;
export const DraftsList: React.FC = () => {
const navigate = useNavigate();
// Состояние фильтра дат: по умолчанию последние 7 дней
const [startDate, setStartDate] = useState<Dayjs>(dayjs().subtract(7, "day"));
const [endDate, setEndDate] = useState<Dayjs>(dayjs());
const [syncLoading, setSyncLoading] = useState(false);
// Запрос данных с учетом дат (даты в ключе обеспечивают авто-перезапрос)
const {
data: invoices,
isLoading,
isError,
refetch,
} = useQuery({
queryKey: [
"drafts",
startDate.format("YYYY-MM-DD"),
endDate.format("YYYY-MM-DD"),
],
queryFn: () =>
api.getDrafts(
startDate.format("YYYY-MM-DD"),
endDate.format("YYYY-MM-DD")
),
staleTime: 0,
refetchOnMount: true,
refetchOnWindowFocus: true,
});
const handleSync = async () => {
setSyncLoading(true);
try {
await api.syncInvoices();
refetch();
} finally {
setSyncLoading(false);
}
};
const getStatusTag = (item: UnifiedInvoice) => {
switch (item.status) {
case "PROCESSING":
return (
<Tag icon={<LoadingOutlined />} color="blue">
Обработка
</Tag>
);
case "READY_TO_VERIFY":
return (
<Tag icon={<ExclamationCircleOutlined />} color="orange">
Проверка
</Tag>
);
case "COMPLETED":
return (
<Tag icon={<CheckCircleOutlined />} color="green">
Готово
</Tag>
);
case "ERROR":
return (
<Tag icon={<CloseCircleOutlined />} color="red">
Ошибка
</Tag>
);
case "CANCELED":
return (
<Tag icon={<StopOutlined />} color="default">
Отменен
</Tag>
);
case "NEW":
return (
<Tag icon={<PlusOutlined />} color="blue">
Новая
</Tag>
);
case "PROCESSED":
return (
<Tag icon={<CheckCircleOutlined />} color="green">
Проведена
</Tag>
);
case "DELETED":
return (
<Tag icon={<DeleteOutlined />} color="red">
Удалена
</Tag>
);
default:
return <Tag>{item.status}</Tag>;
}
};
const handleInvoiceClick = (item: UnifiedInvoice) => {
if (item.type === "DRAFT") {
navigate("/invoice/draft/" + item.id);
} else if (item.type === "SYNCED") {
navigate("/invoice/view/" + item.id);
}
};
if (isError) {
return (
<div style={{ padding: 20 }}>
<Text type="danger">Ошибка загрузки списка накладных</Text>
</div>
);
}
return (
<div style={{ padding: "0 4px 20px" }}>
<Flex align="center" gap={8} style={{ marginTop: 16, marginBottom: 16 }}>
<Title level={4} style={{ margin: 0 }}>
Накладные
</Title>
<Button
icon={<SyncOutlined />}
loading={syncLoading}
onClick={handleSync}
/>
</Flex>
{/* Фильтр дат */}
<div
style={{
marginBottom: 16,
background: "#fff",
padding: 12,
borderRadius: 8,
}}
>
<Text
type="secondary"
style={{ display: "block", marginBottom: 8, fontSize: 12 }}
>
Период загрузки:
</Text>
<Flex gap={8}>
<DatePicker
value={startDate}
onChange={(date) => date && setStartDate(date)}
style={{ flex: 1 }}
placeholder="Начало"
format="DD.MM.YYYY"
allowClear={false}
/>
<DatePicker
value={endDate}
onChange={(date) => date && setEndDate(date)}
style={{ flex: 1 }}
placeholder="Конец"
format="DD.MM.YYYY"
allowClear={false}
/>
</Flex>
</div>
{isLoading ? (
<div style={{ textAlign: "center", padding: 40 }}>
<Spin size="large" />
</div>
) : !invoices || invoices.length === 0 ? (
<Empty description="Нет данных за выбранный период" />
) : (
<List
dataSource={invoices}
renderItem={(item) => {
const isSynced = item.type === "SYNCED";
const displayDate =
item.type === "DRAFT" ? item.created_at : item.date_incoming;
return (
<List.Item
style={{
background: isSynced ? "#fafafa" : "#fff",
padding: 12,
marginBottom: 10,
borderRadius: 12,
cursor: "pointer",
border: isSynced ? "1px solid #f0f0f0" : "1px solid #e6f7ff",
boxShadow: "0 2px 4px rgba(0,0,0,0.02)",
display: "block",
}}
onClick={() => handleInvoiceClick(item)}
>
<Flex vertical gap={4}>
<Flex justify="space-between" align="start">
<Flex vertical>
<Flex align="center" gap={8}>
<Text strong style={{ fontSize: 16 }}>
{item.document_number || "Без номера"}
</Text>
{item.type === "SYNCED" && (
<CloudServerOutlined style={{ color: "gray" }} />
)}
{item.is_app_created && (
<span title="Создано в RMSer">📱</span>
)}
</Flex>
<Flex vertical gap={2}>
<Text type="secondary" style={{ fontSize: 13 }}>
{item.items_count} поз.
</Text>
<Text type="secondary" style={{ fontSize: 13 }}>
{dayjs(displayDate).format("DD.MM.YYYY")}
</Text>
</Flex>
{item.incoming_number && (
<Text type="secondary" style={{ fontSize: 12 }}>
Вх. {item.incoming_number}
</Text>
)}
</Flex>
<Flex vertical align="start" gap={4}>
{getStatusTag(item)}
{isSynced && item.items_preview && (
<div>
{item.items_preview
.split(", ")
.map((previewItem, idx) => (
<div
key={idx}
style={{ fontSize: 12, color: "#666" }}
>
{previewItem}
</div>
))}
</div>
)}
</Flex>
</Flex>
<Flex justify="space-between" align="center">
<div></div>
{item.store_name && (
<Tag
style={{
margin: 0,
maxWidth: 120,
overflow: "hidden",
textOverflow: "ellipsis",
}}
>
{item.store_name}
</Tag>
)}
</Flex>
<Flex
justify="space-between"
align="center"
style={{ marginTop: 8 }}
>
<Text
strong
style={{
fontSize: 17,
color: isSynced ? "#595959" : "#1890ff",
}}
>
{item.total_sum.toLocaleString("ru-RU", {
style: "currency",
currency: "RUB",
maximumFractionDigits: 0,
})}
</Text>
{!isSynced && (
<ArrowRightOutlined style={{ color: "#1890ff" }} />
)}
</Flex>
</Flex>
</List.Item>
);
}}
/>
)}
</div>
);
};