mirror of
https://github.com/serty2005/rmser.git
synced 2026-02-04 19:02:33 -06:00
2801-опция для перетаскивания строк в черновике.
пофиксил синк накладных свайп убрал внешний номер теперь ок
This commit is contained in:
@@ -573,7 +573,7 @@ func (c *Client) CreateIncomingInvoice(inv invoices.Invoice) (string, error) {
|
|||||||
|
|
||||||
reqDTO := IncomingInvoiceImportXML{
|
reqDTO := IncomingInvoiceImportXML{
|
||||||
DocumentNumber: inv.DocumentNumber,
|
DocumentNumber: inv.DocumentNumber,
|
||||||
IncomingDocumentNumber: inv.IncomingDocumentNumber, // Присваиваем входящий номер документа из домена
|
IncomingDocumentNumber: inv.IncomingDocumentNumber,
|
||||||
DateIncoming: inv.DateIncoming.Format("02.01.2006"),
|
DateIncoming: inv.DateIncoming.Format("02.01.2006"),
|
||||||
DefaultStore: inv.DefaultStoreID.String(),
|
DefaultStore: inv.DefaultStoreID.String(),
|
||||||
Supplier: inv.SupplierID.String(),
|
Supplier: inv.SupplierID.String(),
|
||||||
@@ -581,6 +581,13 @@ func (c *Client) CreateIncomingInvoice(inv invoices.Invoice) (string, error) {
|
|||||||
Comment: comment,
|
Comment: comment,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.Log.Info("RMS Invoice Import Debug",
|
||||||
|
zap.String("document_number", inv.DocumentNumber),
|
||||||
|
zap.String("incoming_document_number", inv.IncomingDocumentNumber),
|
||||||
|
zap.String("supplier_id", inv.SupplierID.String()),
|
||||||
|
zap.String("store_id", inv.DefaultStoreID.String()),
|
||||||
|
)
|
||||||
|
|
||||||
if inv.ID != uuid.Nil {
|
if inv.ID != uuid.Nil {
|
||||||
reqDTO.ID = inv.ID.String()
|
reqDTO.ID = inv.ID.String()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -422,6 +422,14 @@ func (s *Service) CommitDraft(draftID, userID uuid.UUID) (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Log.Warn("Не удалось получить список накладных для поиска UUID", zap.Error(err), zap.Time("date", *draft.DateIncoming))
|
logger.Log.Warn("Не удалось получить список накладных для поиска UUID", zap.Error(err), zap.Time("date", *draft.DateIncoming))
|
||||||
} else {
|
} else {
|
||||||
|
// ВАЖНО: Сохраняем полученные накладные, чтобы они сразу появились в базе как SYNCED
|
||||||
|
for i := range invoices {
|
||||||
|
invoices[i].RMSServerID = server.ID
|
||||||
|
}
|
||||||
|
if err := s.invoiceRepo.SaveInvoices(invoices); err != nil {
|
||||||
|
logger.Log.Error("Failed to save committed invoices", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
for _, invoice := range invoices {
|
for _, invoice := range invoices {
|
||||||
if invoice.DocumentNumber == docNum {
|
if invoice.DocumentNumber == docNum {
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ func (s *Service) syncInvoices(c rms.ClientI, serverID uuid.UUID, force bool) er
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if lastDate != nil {
|
if lastDate != nil {
|
||||||
from = *lastDate
|
from = lastDate.AddDate(0, 0, -7)
|
||||||
} else {
|
} else {
|
||||||
from = time.Now().AddDate(0, 0, -45)
|
from = time.Now().AddDate(0, 0, -45)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ type CommitRequestDTO struct {
|
|||||||
StoreID string `json:"store_id"`
|
StoreID string `json:"store_id"`
|
||||||
SupplierID string `json:"supplier_id"`
|
SupplierID string `json:"supplier_id"`
|
||||||
Comment string `json:"comment"`
|
Comment string `json:"comment"`
|
||||||
IncomingDocNum string `json:"incoming_doc_num"`
|
IncomingDocNum string `json:"incoming_document_number"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *DraftsHandler) CommitDraft(c *gin.Context) {
|
func (h *DraftsHandler) CommitDraft(c *gin.Context) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# ===================================================================
|
# ===================================================================
|
||||||
# Полный контекст React Typescript проекта
|
# Полный контекст React Typescript проекта
|
||||||
# Сгенерировано: 2026-01-27 11:40:29
|
# Сгенерировано: 2026-01-28 02:48:31
|
||||||
# ===================================================================
|
# ===================================================================
|
||||||
|
|
||||||
Это полный дамп исходного кода React Typescript (Vite) проекта.
|
Это полный дамп исходного кода React Typescript (Vite) проекта.
|
||||||
@@ -5106,6 +5106,7 @@ interface Props {
|
|||||||
onDelete: (itemId: string) => void;
|
onDelete: (itemId: string) => void;
|
||||||
isUpdating: boolean;
|
isUpdating: boolean;
|
||||||
recommendations?: Recommendation[];
|
recommendations?: Recommendation[];
|
||||||
|
isReordering: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
type FieldType = "quantity" | "price" | "sum";
|
type FieldType = "quantity" | "price" | "sum";
|
||||||
@@ -5117,6 +5118,7 @@ export const DraftItemRow: React.FC<Props> = ({
|
|||||||
onDelete,
|
onDelete,
|
||||||
isUpdating,
|
isUpdating,
|
||||||
recommendations = [],
|
recommendations = [],
|
||||||
|
isReordering,
|
||||||
}) => {
|
}) => {
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
|
||||||
@@ -5359,224 +5361,228 @@ export const DraftItemRow: React.FC<Props> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Draggable draggableId={item.id} index={index}>
|
<Draggable draggableId={item.id} index={index} isDragDisabled={!isReordering}>
|
||||||
{(provided, snapshot) => (
|
{(provided, snapshot) => {
|
||||||
<div
|
const style = {
|
||||||
ref={provided.innerRef}
|
marginBottom: "8px",
|
||||||
{...provided.draggableProps}
|
backgroundColor: snapshot.isDragging ? "#e6f7ff" : "transparent",
|
||||||
style={{
|
boxShadow: snapshot.isDragging
|
||||||
...provided.draggableProps.style,
|
? "0 4px 12px rgba(0, 0, 0, 0.15)"
|
||||||
display: "table",
|
: "none",
|
||||||
width: "100%",
|
borderRadius: "4px",
|
||||||
marginBottom: "8px",
|
transition: "background-color 0.2s ease, box-shadow 0.2s ease",
|
||||||
backgroundColor: snapshot.isDragging ? "#e6f7ff" : "transparent",
|
...provided.draggableProps.style,
|
||||||
boxShadow: snapshot.isDragging
|
};
|
||||||
? "0 4px 12px rgba(0, 0, 0, 0.15)"
|
|
||||||
: "none",
|
return (
|
||||||
borderRadius: "4px",
|
<div
|
||||||
transition: "background-color 0.2s ease, box-shadow 0.2s ease",
|
ref={provided.innerRef}
|
||||||
}}
|
{...provided.draggableProps}
|
||||||
>
|
style={style}
|
||||||
<Card
|
|
||||||
size="small"
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
padding: "12px 16px",
|
|
||||||
borderLeft: `4px solid ${cardBorderColor}`,
|
|
||||||
border: snapshot.isDragging
|
|
||||||
? "2px solid #1890ff"
|
|
||||||
: "1px solid #d9d9d9",
|
|
||||||
background: item.product_id ? "#fff" : "#fff1f0",
|
|
||||||
borderRadius: "4px",
|
|
||||||
}}
|
|
||||||
bodyStyle={{ padding: 0 }}
|
|
||||||
>
|
>
|
||||||
{/* Drag handle - иконка для перетаскивания */}
|
<Card
|
||||||
<div
|
size="small"
|
||||||
{...provided.dragHandleProps}
|
|
||||||
style={{
|
style={{
|
||||||
cursor: "grab",
|
|
||||||
padding: "4px 8px 4px 0",
|
|
||||||
color: "#8c8c8c",
|
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
transition: "color 0.2s ease",
|
padding: "12px 16px",
|
||||||
}}
|
borderLeft: `4px solid ${cardBorderColor}`,
|
||||||
onMouseEnter={(e) => {
|
border: snapshot.isDragging
|
||||||
e.currentTarget.style.color = "#1890ff";
|
? "2px solid #1890ff"
|
||||||
}}
|
: "1px solid #d9d9d9",
|
||||||
onMouseLeave={(e) => {
|
background: item.product_id ? "#fff" : "#fff1f0",
|
||||||
e.currentTarget.style.color = "#8c8c8c";
|
borderRadius: "4px",
|
||||||
}}
|
}}
|
||||||
|
bodyStyle={{ padding: 0 }}
|
||||||
>
|
>
|
||||||
<GripVertical size={20} />
|
{/* Drag handle - иконка для перетаскивания (показываем только в режиме перетаскивания) */}
|
||||||
</div>
|
{isReordering && (
|
||||||
|
|
||||||
<Flex vertical gap={10} style={{ flex: 1 }}>
|
|
||||||
<Flex justify="space-between" align="start">
|
|
||||||
<div style={{ flex: 1 }}>
|
|
||||||
<Text
|
|
||||||
type="secondary"
|
|
||||||
style={{
|
|
||||||
fontSize: 12,
|
|
||||||
lineHeight: 1.2,
|
|
||||||
display: "block",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{item.raw_name || "Новая позиция"}
|
|
||||||
</Text>
|
|
||||||
{item.raw_amount > 0 && (
|
|
||||||
<Text
|
|
||||||
type="secondary"
|
|
||||||
style={{ fontSize: 10, display: "block" }}
|
|
||||||
>
|
|
||||||
(чек: {item.raw_amount} x {item.raw_price})
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
|
{...provided.dragHandleProps}
|
||||||
style={{
|
style={{
|
||||||
marginLeft: 8,
|
cursor: "grab",
|
||||||
|
padding: "4px 8px 4px 0",
|
||||||
|
color: "#8c8c8c",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: 6,
|
transition: "color 0.2s ease",
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.color = "#1890ff";
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.color = "#8c8c8c";
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isUpdating && (
|
<GripVertical size={20} />
|
||||||
<SyncOutlined spin style={{ color: "#1890ff" }} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{activeWarning && (
|
|
||||||
<WarningFilled
|
|
||||||
style={{
|
|
||||||
color: "#faad14",
|
|
||||||
fontSize: 16,
|
|
||||||
cursor: "pointer",
|
|
||||||
}}
|
|
||||||
onClick={showWarningModal}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!item.product_id && (
|
|
||||||
<Tag color="error" style={{ margin: 0 }}>
|
|
||||||
?
|
|
||||||
</Tag>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Popconfirm
|
|
||||||
title="Удалить строку?"
|
|
||||||
onConfirm={() => onDelete(item.id)}
|
|
||||||
okText="Да"
|
|
||||||
cancelText="Нет"
|
|
||||||
placement="left"
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
size="small"
|
|
||||||
icon={<DeleteOutlined />}
|
|
||||||
danger
|
|
||||||
style={{ marginLeft: 4 }}
|
|
||||||
/>
|
|
||||||
</Popconfirm>
|
|
||||||
</div>
|
</div>
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<CatalogSelect
|
|
||||||
value={item.product_id || undefined}
|
|
||||||
onChange={handleProductChange}
|
|
||||||
initialProduct={activeProduct}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{activeProduct && (
|
|
||||||
<Select
|
|
||||||
style={{ width: "100%" }}
|
|
||||||
placeholder="Выберите единицу измерения"
|
|
||||||
options={containerOptions}
|
|
||||||
value={item.container_id || "BASE_UNIT"}
|
|
||||||
onChange={handleContainerChange}
|
|
||||||
dropdownRender={(menu) => (
|
|
||||||
<>
|
|
||||||
{menu}
|
|
||||||
<Divider style={{ margin: "4px 0" }} />
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
block
|
|
||||||
icon={<PlusOutlined />}
|
|
||||||
onClick={() => setIsModalOpen(true)}
|
|
||||||
style={{ textAlign: "left" }}
|
|
||||||
>
|
|
||||||
Добавить фасовку...
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div
|
<Flex vertical gap={10} style={{ flex: 1 }}>
|
||||||
style={{
|
<Flex justify="space-between" align="start">
|
||||||
display: "flex",
|
<div style={{ flex: 1 }}>
|
||||||
alignItems: "center",
|
<Text
|
||||||
justifyContent: "space-between",
|
type="secondary"
|
||||||
background: "#fafafa",
|
style={{
|
||||||
margin: "0 -12px -12px -12px",
|
fontSize: 12,
|
||||||
padding: "8px 12px",
|
lineHeight: 1.2,
|
||||||
borderTop: "1px solid #f0f0f0",
|
display: "block",
|
||||||
borderBottomLeftRadius: 8,
|
}}
|
||||||
borderBottomRightRadius: 8,
|
>
|
||||||
}}
|
{item.raw_name || "Новая позиция"}
|
||||||
>
|
</Text>
|
||||||
|
{item.raw_amount > 0 && (
|
||||||
|
<Text
|
||||||
|
type="secondary"
|
||||||
|
style={{ fontSize: 10, display: "block" }}
|
||||||
|
>
|
||||||
|
(чек: {item.raw_amount} x {item.raw_price})
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginLeft: 8,
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 6,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isUpdating && (
|
||||||
|
<SyncOutlined spin style={{ color: "#1890ff" }} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeWarning && (
|
||||||
|
<WarningFilled
|
||||||
|
style={{
|
||||||
|
color: "#faad14",
|
||||||
|
fontSize: 16,
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
onClick={showWarningModal}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!item.product_id && (
|
||||||
|
<Tag color="error" style={{ margin: 0 }}>
|
||||||
|
?
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Popconfirm
|
||||||
|
title="Удалить строку?"
|
||||||
|
onConfirm={() => onDelete(item.id)}
|
||||||
|
okText="Да"
|
||||||
|
cancelText="Нет"
|
||||||
|
placement="left"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
danger
|
||||||
|
style={{ marginLeft: 4 }}
|
||||||
|
/>
|
||||||
|
</Popconfirm>
|
||||||
|
</div>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<CatalogSelect
|
||||||
|
value={item.product_id || undefined}
|
||||||
|
onChange={handleProductChange}
|
||||||
|
initialProduct={activeProduct}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{activeProduct && (
|
||||||
|
<Select
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
placeholder="Выберите единицу измерения"
|
||||||
|
options={containerOptions}
|
||||||
|
value={item.container_id || "BASE_UNIT"}
|
||||||
|
onChange={handleContainerChange}
|
||||||
|
dropdownRender={(menu) => (
|
||||||
|
<>
|
||||||
|
{menu}
|
||||||
|
<Divider style={{ margin: "4px 0" }} />
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
block
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
onClick={() => setIsModalOpen(true)}
|
||||||
|
style={{ textAlign: "left" }}
|
||||||
|
>
|
||||||
|
Добавить фасовку...
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
gap: 8,
|
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
flex: 1,
|
justifyContent: "space-between",
|
||||||
|
background: "#fafafa",
|
||||||
|
margin: "0 -12px -12px -12px",
|
||||||
|
padding: "8px 12px",
|
||||||
|
borderTop: "1px solid #f0f0f0",
|
||||||
|
borderBottomLeftRadius: 8,
|
||||||
|
borderBottomRightRadius: 8,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<InputNumber
|
<div
|
||||||
style={{ width: 70 }}
|
style={{
|
||||||
controls={false}
|
display: "flex",
|
||||||
placeholder="Кол"
|
gap: 8,
|
||||||
min={0}
|
alignItems: "center",
|
||||||
value={localQty}
|
flex: 1,
|
||||||
onChange={(val) => handleValueChange("quantity", val)}
|
}}
|
||||||
onBlur={() => handleBlur("quantity")}
|
>
|
||||||
precision={3}
|
<InputNumber
|
||||||
/>
|
style={{ width: 70 }}
|
||||||
<Text type="secondary">x</Text>
|
controls={false}
|
||||||
<InputNumber
|
placeholder="Кол"
|
||||||
style={{ width: 80 }}
|
min={0}
|
||||||
controls={false}
|
value={localQty}
|
||||||
placeholder="Цена"
|
onChange={(val) => handleValueChange("quantity", val)}
|
||||||
min={0}
|
onBlur={() => handleBlur("quantity")}
|
||||||
value={localPrice}
|
precision={3}
|
||||||
onChange={(val) => handleValueChange("price", val)}
|
/>
|
||||||
onBlur={() => handleBlur("price")}
|
<Text type="secondary">x</Text>
|
||||||
precision={2}
|
<InputNumber
|
||||||
/>
|
style={{ width: 80 }}
|
||||||
</div>
|
controls={false}
|
||||||
|
placeholder="Цена"
|
||||||
|
min={0}
|
||||||
|
value={localPrice}
|
||||||
|
onChange={(val) => handleValueChange("price", val)}
|
||||||
|
onBlur={() => handleBlur("price")}
|
||||||
|
precision={2}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={{ display: "flex", alignItems: "center", gap: 4 }}
|
style={{ display: "flex", alignItems: "center", gap: 4 }}
|
||||||
>
|
>
|
||||||
<Text type="secondary">=</Text>
|
<Text type="secondary">=</Text>
|
||||||
<InputNumber
|
<InputNumber
|
||||||
style={{ width: 90, fontWeight: "bold" }}
|
style={{ width: 90, fontWeight: "bold" }}
|
||||||
controls={false}
|
controls={false}
|
||||||
placeholder="Сумма"
|
placeholder="Сумма"
|
||||||
min={0}
|
min={0}
|
||||||
value={localSum}
|
value={localSum}
|
||||||
onChange={(val) => handleValueChange("sum", val)}
|
onChange={(val) => handleValueChange("sum", val)}
|
||||||
onBlur={() => handleBlur("sum")}
|
onBlur={() => handleBlur("sum")}
|
||||||
precision={2}
|
precision={2}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Flex>
|
||||||
</Flex>
|
</Card>
|
||||||
</Card>
|
</div>
|
||||||
</div>
|
);
|
||||||
)}
|
}}
|
||||||
</Draggable>
|
</Draggable>
|
||||||
{activeProduct && (
|
{activeProduct && (
|
||||||
<CreateContainerModal
|
<CreateContainerModal
|
||||||
@@ -7135,7 +7141,7 @@ export const Dashboard: React.FC = () => {
|
|||||||
```
|
```
|
||||||
// src/pages/DraftsList.tsx
|
// src/pages/DraftsList.tsx
|
||||||
|
|
||||||
import React, { useState, useMemo, useCallback, useRef } from "react";
|
import React, { useState, useMemo } from "react";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import {
|
import {
|
||||||
List,
|
List,
|
||||||
@@ -7209,9 +7215,6 @@ export const DraftsList: React.FC = () => {
|
|||||||
);
|
);
|
||||||
const [endDate, setEndDate] = useState<dayjs.Dayjs | null>(dayjs());
|
const [endDate, setEndDate] = useState<dayjs.Dayjs | null>(dayjs());
|
||||||
|
|
||||||
const touchStartX = useRef<number>(0);
|
|
||||||
const touchEndX = useRef<number>(0);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: invoices,
|
data: invoices,
|
||||||
isLoading,
|
isLoading,
|
||||||
@@ -7316,14 +7319,6 @@ export const DraftsList: React.FC = () => {
|
|||||||
setCurrentPage(1);
|
setCurrentPage(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTouchStart = useCallback((e: React.TouchEvent) => {
|
|
||||||
touchStartX.current = e.changedTouches[0].screenX;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleTouchMove = useCallback((e: React.TouchEvent) => {
|
|
||||||
touchEndX.current = e.changedTouches[0].screenX;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const getItemDate = (item: UnifiedInvoice) =>
|
const getItemDate = (item: UnifiedInvoice) =>
|
||||||
item.type === "DRAFT" ? item.created_at : item.date_incoming;
|
item.type === "DRAFT" ? item.created_at : item.date_incoming;
|
||||||
|
|
||||||
@@ -7361,21 +7356,6 @@ export const DraftsList: React.FC = () => {
|
|||||||
return result;
|
return result;
|
||||||
}, [invoices, filterType]);
|
}, [invoices, filterType]);
|
||||||
|
|
||||||
const handleTouchEnd = useCallback(() => {
|
|
||||||
const diff = touchStartX.current - touchEndX.current;
|
|
||||||
const totalPages = Math.ceil(
|
|
||||||
(filteredAndSortedInvoices.length || 0) / pageSize
|
|
||||||
);
|
|
||||||
|
|
||||||
if (Math.abs(diff) > 50) {
|
|
||||||
if (diff > 0 && currentPage < totalPages) {
|
|
||||||
setCurrentPage((prev) => prev + 1);
|
|
||||||
} else if (diff < 0 && currentPage > 1) {
|
|
||||||
setCurrentPage((prev) => prev - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [currentPage, pageSize, filteredAndSortedInvoices]);
|
|
||||||
|
|
||||||
const paginatedInvoices = useMemo(() => {
|
const paginatedInvoices = useMemo(() => {
|
||||||
const startIndex = (currentPage - 1) * pageSize;
|
const startIndex = (currentPage - 1) * pageSize;
|
||||||
return filteredAndSortedInvoices.slice(startIndex, startIndex + pageSize);
|
return filteredAndSortedInvoices.slice(startIndex, startIndex + pageSize);
|
||||||
@@ -7482,11 +7462,7 @@ export const DraftsList: React.FC = () => {
|
|||||||
<Empty description="Нет данных" />
|
<Empty description="Нет данных" />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div
|
<div>
|
||||||
onTouchStart={handleTouchStart}
|
|
||||||
onTouchMove={handleTouchMove}
|
|
||||||
onTouchEnd={handleTouchEnd}
|
|
||||||
>
|
|
||||||
{Object.entries(groupedInvoices).map(([dateKey, items]) => (
|
{Object.entries(groupedInvoices).map(([dateKey, items]) => (
|
||||||
<div key={dateKey}>
|
<div key={dateKey}>
|
||||||
<DayDivider date={dateKey} />
|
<DayDivider date={dateKey} />
|
||||||
@@ -7707,6 +7683,7 @@ import {
|
|||||||
RestOutlined,
|
RestOutlined,
|
||||||
PlusOutlined,
|
PlusOutlined,
|
||||||
FileImageOutlined,
|
FileImageOutlined,
|
||||||
|
SwapOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { api, getStaticUrl } from "../services/api";
|
import { api, getStaticUrl } from "../services/api";
|
||||||
@@ -7730,6 +7707,8 @@ export const InvoiceDraftPage: React.FC = () => {
|
|||||||
|
|
||||||
const [updatingItems, setUpdatingItems] = useState<Set<string>>(new Set());
|
const [updatingItems, setUpdatingItems] = useState<Set<string>>(new Set());
|
||||||
const [itemsOrder, setItemsOrder] = useState<Record<string, number>>({});
|
const [itemsOrder, setItemsOrder] = useState<Record<string, number>>({});
|
||||||
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
|
const [isReordering, setIsReordering] = useState(false);
|
||||||
|
|
||||||
// Состояние для просмотра фото чека
|
// Состояние для просмотра фото чека
|
||||||
const [previewVisible, setPreviewVisible] = useState(false);
|
const [previewVisible, setPreviewVisible] = useState(false);
|
||||||
@@ -7752,6 +7731,7 @@ export const InvoiceDraftPage: React.FC = () => {
|
|||||||
queryFn: () => api.getDraft(id!),
|
queryFn: () => api.getDraft(id!),
|
||||||
enabled: !!id,
|
enabled: !!id,
|
||||||
refetchInterval: (query) => {
|
refetchInterval: (query) => {
|
||||||
|
if (isDragging) return false;
|
||||||
const status = query.state.data?.status;
|
const status = query.state.data?.status;
|
||||||
return status === "PROCESSING" ? 3000 : false;
|
return status === "PROCESSING" ? 3000 : false;
|
||||||
},
|
},
|
||||||
@@ -7849,6 +7829,7 @@ export const InvoiceDraftPage: React.FC = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// --- ЭФФЕКТЫ ---
|
// --- ЭФФЕКТЫ ---
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (draft) {
|
if (draft) {
|
||||||
const currentValues = form.getFieldsValue();
|
const currentValues = form.getFieldsValue();
|
||||||
@@ -7939,7 +7920,12 @@ export const InvoiceDraftPage: React.FC = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDragStart = () => {
|
||||||
|
setIsDragging(true);
|
||||||
|
};
|
||||||
|
|
||||||
const handleDragEnd = async (result: DropResult) => {
|
const handleDragEnd = async (result: DropResult) => {
|
||||||
|
setIsDragging(false);
|
||||||
const { source, destination } = result;
|
const { source, destination } = result;
|
||||||
|
|
||||||
// Если нет назначения или позиция не изменилась
|
// Если нет назначения или позиция не изменилась
|
||||||
@@ -8059,7 +8045,7 @@ export const InvoiceDraftPage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Правая часть хедера: Кнопка чека и Кнопка удаления */}
|
{/* Правая часть хедера: Кнопка чека, Кнопка перетаскивания и Кнопка удаления */}
|
||||||
<div style={{ display: "flex", gap: 8 }}>
|
<div style={{ display: "flex", gap: 8 }}>
|
||||||
{/* Кнопка просмотра чека (только если есть URL) */}
|
{/* Кнопка просмотра чека (только если есть URL) */}
|
||||||
{draft.photo_url && (
|
{draft.photo_url && (
|
||||||
@@ -8072,6 +8058,16 @@ export const InvoiceDraftPage: React.FC = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Кнопка переключения режима перетаскивания */}
|
||||||
|
<Button
|
||||||
|
type={isReordering ? "primary" : "default"}
|
||||||
|
icon={<SwapOutlined rotate={90} />}
|
||||||
|
onClick={() => setIsReordering(!isReordering)}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{isReordering ? "Ок" : ""}
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
danger={isCanceled}
|
danger={isCanceled}
|
||||||
type={isCanceled ? "primary" : "default"}
|
type={isCanceled ? "primary" : "default"}
|
||||||
@@ -8195,7 +8191,7 @@ export const InvoiceDraftPage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Items List */}
|
{/* Items List */}
|
||||||
<DragDropContext onDragEnd={handleDragEnd}>
|
<DragDropContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
|
||||||
<Droppable droppableId="draft-items">
|
<Droppable droppableId="draft-items">
|
||||||
{(provided, snapshot) => (
|
{(provided, snapshot) => (
|
||||||
<div
|
<div
|
||||||
@@ -8222,6 +8218,7 @@ export const InvoiceDraftPage: React.FC = () => {
|
|||||||
onDelete={(itemId) => deleteItemMutation.mutate(itemId)}
|
onDelete={(itemId) => deleteItemMutation.mutate(itemId)}
|
||||||
isUpdating={updatingItems.has(item.id)}
|
isUpdating={updatingItems.has(item.id)}
|
||||||
recommendations={recommendationsQuery.data || []}
|
recommendations={recommendationsQuery.data || []}
|
||||||
|
isReordering={isReordering}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{provided.placeholder}
|
{provided.placeholder}
|
||||||
@@ -8700,6 +8697,7 @@ import {
|
|||||||
Spin,
|
Spin,
|
||||||
message,
|
message,
|
||||||
Tabs,
|
Tabs,
|
||||||
|
Popconfirm,
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import {
|
import {
|
||||||
SaveOutlined,
|
SaveOutlined,
|
||||||
@@ -8758,6 +8756,17 @@ export const SettingsPage: React.FC = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const deleteAllDraftsMutation = useMutation({
|
||||||
|
mutationFn: () => api.deleteAllDrafts(),
|
||||||
|
onSuccess: (data) => {
|
||||||
|
message.success(`Удалено черновиков: ${data.count}`);
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["stats"] });
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
message.error("Не удалось удалить черновики");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// --- Эффекты ---
|
// --- Эффекты ---
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -8871,6 +8880,33 @@ export const SettingsPage: React.FC = () => {
|
|||||||
>
|
>
|
||||||
Сохранить настройки
|
Сохранить настройки
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
{currentUserRole === "OWNER" && (
|
||||||
|
<Card
|
||||||
|
size="small"
|
||||||
|
style={{
|
||||||
|
marginTop: 24,
|
||||||
|
borderColor: "#ff4d4f",
|
||||||
|
borderWidth: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Title level={5} style={{ color: "#ff4d4f", marginBottom: 16 }}>
|
||||||
|
Опасная зона
|
||||||
|
</Title>
|
||||||
|
<Popconfirm
|
||||||
|
title="Вы уверены?"
|
||||||
|
description="Это удалит ВСЕ черновики, которые еще не были отправлены в iiko. Это действие необратимо."
|
||||||
|
onConfirm={() => deleteAllDraftsMutation.mutate()}
|
||||||
|
okText="Удалить"
|
||||||
|
cancelText="Отмена"
|
||||||
|
okButtonProps={{ danger: true }}
|
||||||
|
>
|
||||||
|
<Button danger block loading={deleteAllDraftsMutation.isPending}>
|
||||||
|
Удалить все черновики
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -9193,6 +9229,11 @@ export const api = {
|
|||||||
deleteDraft: async (id: string): Promise<void> => {
|
deleteDraft: async (id: string): Promise<void> => {
|
||||||
await apiClient.delete(`/drafts/${id}`);
|
await apiClient.delete(`/drafts/${id}`);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
deleteAllDrafts: async (): Promise<{ count: number }> => {
|
||||||
|
const { data } = await apiClient.delete<{ count: number }>('/drafts');
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
// --- Настройки и Статистика ---
|
// --- Настройки и Статистика ---
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ interface Props {
|
|||||||
onDelete: (itemId: string) => void;
|
onDelete: (itemId: string) => void;
|
||||||
isUpdating: boolean;
|
isUpdating: boolean;
|
||||||
recommendations?: Recommendation[];
|
recommendations?: Recommendation[];
|
||||||
|
isReordering: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
type FieldType = "quantity" | "price" | "sum";
|
type FieldType = "quantity" | "price" | "sum";
|
||||||
@@ -49,6 +50,7 @@ export const DraftItemRow: React.FC<Props> = ({
|
|||||||
onDelete,
|
onDelete,
|
||||||
isUpdating,
|
isUpdating,
|
||||||
recommendations = [],
|
recommendations = [],
|
||||||
|
isReordering,
|
||||||
}) => {
|
}) => {
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
|
||||||
@@ -291,7 +293,7 @@ export const DraftItemRow: React.FC<Props> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Draggable draggableId={item.id} index={index}>
|
<Draggable draggableId={item.id} index={index} isDragDisabled={!isReordering}>
|
||||||
{(provided, snapshot) => {
|
{(provided, snapshot) => {
|
||||||
const style = {
|
const style = {
|
||||||
marginBottom: "8px",
|
marginBottom: "8px",
|
||||||
@@ -325,26 +327,28 @@ export const DraftItemRow: React.FC<Props> = ({
|
|||||||
}}
|
}}
|
||||||
bodyStyle={{ padding: 0 }}
|
bodyStyle={{ padding: 0 }}
|
||||||
>
|
>
|
||||||
{/* Drag handle - иконка для перетаскивания */}
|
{/* Drag handle - иконка для перетаскивания (показываем только в режиме перетаскивания) */}
|
||||||
<div
|
{isReordering && (
|
||||||
{...provided.dragHandleProps}
|
<div
|
||||||
style={{
|
{...provided.dragHandleProps}
|
||||||
cursor: "grab",
|
style={{
|
||||||
padding: "4px 8px 4px 0",
|
cursor: "grab",
|
||||||
color: "#8c8c8c",
|
padding: "4px 8px 4px 0",
|
||||||
display: "flex",
|
color: "#8c8c8c",
|
||||||
alignItems: "center",
|
display: "flex",
|
||||||
transition: "color 0.2s ease",
|
alignItems: "center",
|
||||||
}}
|
transition: "color 0.2s ease",
|
||||||
onMouseEnter={(e) => {
|
}}
|
||||||
e.currentTarget.style.color = "#1890ff";
|
onMouseEnter={(e) => {
|
||||||
}}
|
e.currentTarget.style.color = "#1890ff";
|
||||||
onMouseLeave={(e) => {
|
}}
|
||||||
e.currentTarget.style.color = "#8c8c8c";
|
onMouseLeave={(e) => {
|
||||||
}}
|
e.currentTarget.style.color = "#8c8c8c";
|
||||||
>
|
}}
|
||||||
<GripVertical size={20} />
|
>
|
||||||
</div>
|
<GripVertical size={20} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<Flex vertical gap={10} style={{ flex: 1 }}>
|
<Flex vertical gap={10} style={{ flex: 1 }}>
|
||||||
<Flex justify="space-between" align="start">
|
<Flex justify="space-between" align="start">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// src/pages/DraftsList.tsx
|
// src/pages/DraftsList.tsx
|
||||||
|
|
||||||
import React, { useState, useMemo, useCallback, useRef } from "react";
|
import React, { useState, useMemo } from "react";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import {
|
import {
|
||||||
List,
|
List,
|
||||||
@@ -74,9 +74,6 @@ export const DraftsList: React.FC = () => {
|
|||||||
);
|
);
|
||||||
const [endDate, setEndDate] = useState<dayjs.Dayjs | null>(dayjs());
|
const [endDate, setEndDate] = useState<dayjs.Dayjs | null>(dayjs());
|
||||||
|
|
||||||
const touchStartX = useRef<number>(0);
|
|
||||||
const touchEndX = useRef<number>(0);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: invoices,
|
data: invoices,
|
||||||
isLoading,
|
isLoading,
|
||||||
@@ -181,14 +178,6 @@ export const DraftsList: React.FC = () => {
|
|||||||
setCurrentPage(1);
|
setCurrentPage(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTouchStart = useCallback((e: React.TouchEvent) => {
|
|
||||||
touchStartX.current = e.changedTouches[0].screenX;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleTouchMove = useCallback((e: React.TouchEvent) => {
|
|
||||||
touchEndX.current = e.changedTouches[0].screenX;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const getItemDate = (item: UnifiedInvoice) =>
|
const getItemDate = (item: UnifiedInvoice) =>
|
||||||
item.type === "DRAFT" ? item.created_at : item.date_incoming;
|
item.type === "DRAFT" ? item.created_at : item.date_incoming;
|
||||||
|
|
||||||
@@ -226,21 +215,6 @@ export const DraftsList: React.FC = () => {
|
|||||||
return result;
|
return result;
|
||||||
}, [invoices, filterType]);
|
}, [invoices, filterType]);
|
||||||
|
|
||||||
const handleTouchEnd = useCallback(() => {
|
|
||||||
const diff = touchStartX.current - touchEndX.current;
|
|
||||||
const totalPages = Math.ceil(
|
|
||||||
(filteredAndSortedInvoices.length || 0) / pageSize
|
|
||||||
);
|
|
||||||
|
|
||||||
if (Math.abs(diff) > 50) {
|
|
||||||
if (diff > 0 && currentPage < totalPages) {
|
|
||||||
setCurrentPage((prev) => prev + 1);
|
|
||||||
} else if (diff < 0 && currentPage > 1) {
|
|
||||||
setCurrentPage((prev) => prev - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [currentPage, pageSize, filteredAndSortedInvoices]);
|
|
||||||
|
|
||||||
const paginatedInvoices = useMemo(() => {
|
const paginatedInvoices = useMemo(() => {
|
||||||
const startIndex = (currentPage - 1) * pageSize;
|
const startIndex = (currentPage - 1) * pageSize;
|
||||||
return filteredAndSortedInvoices.slice(startIndex, startIndex + pageSize);
|
return filteredAndSortedInvoices.slice(startIndex, startIndex + pageSize);
|
||||||
@@ -347,11 +321,7 @@ export const DraftsList: React.FC = () => {
|
|||||||
<Empty description="Нет данных" />
|
<Empty description="Нет данных" />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div
|
<div>
|
||||||
onTouchStart={handleTouchStart}
|
|
||||||
onTouchMove={handleTouchMove}
|
|
||||||
onTouchEnd={handleTouchEnd}
|
|
||||||
>
|
|
||||||
{Object.entries(groupedInvoices).map(([dateKey, items]) => (
|
{Object.entries(groupedInvoices).map(([dateKey, items]) => (
|
||||||
<div key={dateKey}>
|
<div key={dateKey}>
|
||||||
<DayDivider date={dateKey} />
|
<DayDivider date={dateKey} />
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import {
|
|||||||
RestOutlined,
|
RestOutlined,
|
||||||
PlusOutlined,
|
PlusOutlined,
|
||||||
FileImageOutlined,
|
FileImageOutlined,
|
||||||
|
SwapOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { api, getStaticUrl } from "../services/api";
|
import { api, getStaticUrl } from "../services/api";
|
||||||
@@ -50,6 +51,7 @@ export const InvoiceDraftPage: React.FC = () => {
|
|||||||
const [updatingItems, setUpdatingItems] = useState<Set<string>>(new Set());
|
const [updatingItems, setUpdatingItems] = useState<Set<string>>(new Set());
|
||||||
const [itemsOrder, setItemsOrder] = useState<Record<string, number>>({});
|
const [itemsOrder, setItemsOrder] = useState<Record<string, number>>({});
|
||||||
const [isDragging, setIsDragging] = useState(false);
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
|
const [isReordering, setIsReordering] = useState(false);
|
||||||
|
|
||||||
// Состояние для просмотра фото чека
|
// Состояние для просмотра фото чека
|
||||||
const [previewVisible, setPreviewVisible] = useState(false);
|
const [previewVisible, setPreviewVisible] = useState(false);
|
||||||
@@ -386,7 +388,7 @@ export const InvoiceDraftPage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Правая часть хедера: Кнопка чека и Кнопка удаления */}
|
{/* Правая часть хедера: Кнопка чека, Кнопка перетаскивания и Кнопка удаления */}
|
||||||
<div style={{ display: "flex", gap: 8 }}>
|
<div style={{ display: "flex", gap: 8 }}>
|
||||||
{/* Кнопка просмотра чека (только если есть URL) */}
|
{/* Кнопка просмотра чека (только если есть URL) */}
|
||||||
{draft.photo_url && (
|
{draft.photo_url && (
|
||||||
@@ -399,6 +401,16 @@ export const InvoiceDraftPage: React.FC = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Кнопка переключения режима перетаскивания */}
|
||||||
|
<Button
|
||||||
|
type={isReordering ? "primary" : "default"}
|
||||||
|
icon={<SwapOutlined rotate={90} />}
|
||||||
|
onClick={() => setIsReordering(!isReordering)}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{isReordering ? "Ок" : ""}
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
danger={isCanceled}
|
danger={isCanceled}
|
||||||
type={isCanceled ? "primary" : "default"}
|
type={isCanceled ? "primary" : "default"}
|
||||||
@@ -549,6 +561,7 @@ export const InvoiceDraftPage: React.FC = () => {
|
|||||||
onDelete={(itemId) => deleteItemMutation.mutate(itemId)}
|
onDelete={(itemId) => deleteItemMutation.mutate(itemId)}
|
||||||
isUpdating={updatingItems.has(item.id)}
|
isUpdating={updatingItems.has(item.id)}
|
||||||
recommendations={recommendationsQuery.data || []}
|
recommendations={recommendationsQuery.data || []}
|
||||||
|
isReordering={isReordering}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{provided.placeholder}
|
{provided.placeholder}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
Spin,
|
Spin,
|
||||||
message,
|
message,
|
||||||
Tabs,
|
Tabs,
|
||||||
|
Popconfirm,
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import {
|
import {
|
||||||
SaveOutlined,
|
SaveOutlined,
|
||||||
@@ -71,6 +72,17 @@ export const SettingsPage: React.FC = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const deleteAllDraftsMutation = useMutation({
|
||||||
|
mutationFn: () => api.deleteAllDrafts(),
|
||||||
|
onSuccess: (data) => {
|
||||||
|
message.success(`Удалено черновиков: ${data.count}`);
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["stats"] });
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
message.error("Не удалось удалить черновики");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// --- Эффекты ---
|
// --- Эффекты ---
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -184,6 +196,33 @@ export const SettingsPage: React.FC = () => {
|
|||||||
>
|
>
|
||||||
Сохранить настройки
|
Сохранить настройки
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
{currentUserRole === "OWNER" && (
|
||||||
|
<Card
|
||||||
|
size="small"
|
||||||
|
style={{
|
||||||
|
marginTop: 24,
|
||||||
|
borderColor: "#ff4d4f",
|
||||||
|
borderWidth: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Title level={5} style={{ color: "#ff4d4f", marginBottom: 16 }}>
|
||||||
|
Опасная зона
|
||||||
|
</Title>
|
||||||
|
<Popconfirm
|
||||||
|
title="Вы уверены?"
|
||||||
|
description="Это удалит ВСЕ черновики, которые еще не были отправлены в iiko. Это действие необратимо."
|
||||||
|
onConfirm={() => deleteAllDraftsMutation.mutate()}
|
||||||
|
okText="Удалить"
|
||||||
|
cancelText="Отмена"
|
||||||
|
okButtonProps={{ danger: true }}
|
||||||
|
>
|
||||||
|
<Button danger block loading={deleteAllDraftsMutation.isPending}>
|
||||||
|
Удалить все черновики
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -221,6 +221,11 @@ export const api = {
|
|||||||
deleteDraft: async (id: string): Promise<void> => {
|
deleteDraft: async (id: string): Promise<void> => {
|
||||||
await apiClient.delete(`/drafts/${id}`);
|
await apiClient.delete(`/drafts/${id}`);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
deleteAllDrafts: async (): Promise<{ count: number }> => {
|
||||||
|
const { data } = await apiClient.delete<{ count: number }>('/drafts');
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
// --- Настройки и Статистика ---
|
// --- Настройки и Статистика ---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user