mirror of
https://github.com/serty2005/rmser.git
synced 2026-02-04 19:02:33 -06:00
234 lines
7.2 KiB
TypeScript
234 lines
7.2 KiB
TypeScript
// src/pages/DraftsList.tsx
|
||
|
||
import React, { useState } from "react";
|
||
import { useQuery } from "@tanstack/react-query";
|
||
import {
|
||
List,
|
||
Typography,
|
||
Tag,
|
||
Spin,
|
||
Empty,
|
||
DatePicker,
|
||
Flex,
|
||
message,
|
||
} from "antd";
|
||
import { useNavigate } from "react-router-dom";
|
||
import {
|
||
ArrowRightOutlined,
|
||
ThunderboltFilled,
|
||
HistoryOutlined,
|
||
FileTextOutlined,
|
||
} from "@ant-design/icons";
|
||
import dayjs, { Dayjs } from "dayjs";
|
||
import { api } from "../services/api";
|
||
import type { UnifiedInvoice } from "../services/types";
|
||
|
||
const { Title, Text } = Typography;
|
||
const { RangePicker } = DatePicker;
|
||
|
||
export const DraftsList: React.FC = () => {
|
||
const navigate = useNavigate();
|
||
|
||
// Состояние фильтра дат: по умолчанию последние 30 дней
|
||
const [dateRange, setDateRange] = useState<[Dayjs, Dayjs]>([
|
||
dayjs().subtract(30, "day"),
|
||
dayjs(),
|
||
]);
|
||
|
||
// Запрос данных с учетом дат (даты в ключе обеспечивают авто-перезапрос)
|
||
const {
|
||
data: invoices,
|
||
isLoading,
|
||
isError,
|
||
} = useQuery({
|
||
queryKey: [
|
||
"drafts",
|
||
dateRange[0].format("YYYY-MM-DD"),
|
||
dateRange[1].format("YYYY-MM-DD"),
|
||
],
|
||
queryFn: () =>
|
||
api.getDrafts(
|
||
dateRange[0].format("YYYY-MM-DD"),
|
||
dateRange[1].format("YYYY-MM-DD")
|
||
),
|
||
});
|
||
|
||
const getStatusTag = (item: UnifiedInvoice) => {
|
||
if (item.type === "SYNCED") {
|
||
return (
|
||
<Tag icon={<HistoryOutlined />} color="success">
|
||
Синхронизировано
|
||
</Tag>
|
||
);
|
||
}
|
||
|
||
switch (item.status) {
|
||
case "PROCESSING":
|
||
return <Tag color="blue">Обработка</Tag>;
|
||
case "READY_TO_VERIFY":
|
||
return <Tag color="orange">Проверка</Tag>;
|
||
case "COMPLETED":
|
||
return <Tag color="green">Готово</Tag>;
|
||
case "ERROR":
|
||
return <Tag color="red">Ошибка</Tag>;
|
||
case "CANCELED":
|
||
return <Tag color="default">Отменен</Tag>;
|
||
default:
|
||
return <Tag>{item.status}</Tag>;
|
||
}
|
||
};
|
||
|
||
const handleInvoiceClick = (item: UnifiedInvoice) => {
|
||
if (item.type === "SYNCED") {
|
||
message.info("История доступна только для просмотра");
|
||
return;
|
||
}
|
||
navigate(`/invoice/${item.id}`);
|
||
};
|
||
|
||
if (isError) {
|
||
return (
|
||
<div style={{ padding: 20 }}>
|
||
<Text type="danger">Ошибка загрузки списка накладных</Text>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div style={{ padding: "0 4px 20px" }}>
|
||
<Title level={4} style={{ marginTop: 16, marginBottom: 16 }}>
|
||
Накладные
|
||
</Title>
|
||
|
||
{/* Фильтр дат */}
|
||
<div
|
||
style={{
|
||
marginBottom: 16,
|
||
background: "#fff",
|
||
padding: 12,
|
||
borderRadius: 8,
|
||
}}
|
||
>
|
||
<Text
|
||
type="secondary"
|
||
style={{ display: "block", marginBottom: 8, fontSize: 12 }}
|
||
>
|
||
Период загрузки:
|
||
</Text>
|
||
<RangePicker
|
||
value={dateRange}
|
||
onChange={(dates) => dates && setDateRange([dates[0]!, dates[1]!])}
|
||
style={{ width: "100%" }}
|
||
allowClear={false}
|
||
format="DD.MM.YYYY"
|
||
/>
|
||
</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";
|
||
|
||
return (
|
||
<List.Item
|
||
style={{
|
||
background: isSynced ? "#fafafa" : "#fff",
|
||
padding: 12,
|
||
marginBottom: 10,
|
||
borderRadius: 12,
|
||
cursor: isSynced ? "default" : "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.is_app_created && (
|
||
<ThunderboltFilled
|
||
style={{ color: "#faad14" }}
|
||
title="Создано в RMSer"
|
||
/>
|
||
)}
|
||
</Flex>
|
||
{item.incoming_number && (
|
||
<Text type="secondary" style={{ fontSize: 12 }}>
|
||
Вх. № {item.incoming_number}
|
||
</Text>
|
||
)}
|
||
</Flex>
|
||
{getStatusTag(item)}
|
||
</Flex>
|
||
|
||
<Flex justify="space-between" style={{ marginTop: 4 }}>
|
||
<Flex gap={8} align="center">
|
||
<FileTextOutlined style={{ color: "#888" }} />
|
||
<Text type="secondary" style={{ fontSize: 13 }}>
|
||
{item.items_count} поз.
|
||
</Text>
|
||
<Text type="secondary" style={{ fontSize: 13 }}>
|
||
•
|
||
</Text>
|
||
<Text type="secondary" style={{ fontSize: 13 }}>
|
||
{dayjs(item.date_incoming).format("DD.MM.YYYY")}
|
||
</Text>
|
||
</Flex>
|
||
{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>
|
||
);
|
||
};
|