mirror of
https://github.com/serty2005/rmser.git
synced 2026-02-04 19:02:33 -06:00
2801-опция для перетаскивания строк в черновике.
пофиксил синк накладных свайп убрал внешний номер теперь ок
This commit is contained in:
@@ -38,6 +38,7 @@ interface Props {
|
||||
onDelete: (itemId: string) => void;
|
||||
isUpdating: boolean;
|
||||
recommendations?: Recommendation[];
|
||||
isReordering: boolean;
|
||||
}
|
||||
|
||||
type FieldType = "quantity" | "price" | "sum";
|
||||
@@ -49,6 +50,7 @@ export const DraftItemRow: React.FC<Props> = ({
|
||||
onDelete,
|
||||
isUpdating,
|
||||
recommendations = [],
|
||||
isReordering,
|
||||
}) => {
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
||||
@@ -291,7 +293,7 @@ export const DraftItemRow: React.FC<Props> = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Draggable draggableId={item.id} index={index}>
|
||||
<Draggable draggableId={item.id} index={index} isDragDisabled={!isReordering}>
|
||||
{(provided, snapshot) => {
|
||||
const style = {
|
||||
marginBottom: "8px",
|
||||
@@ -325,26 +327,28 @@ export const DraftItemRow: React.FC<Props> = ({
|
||||
}}
|
||||
bodyStyle={{ padding: 0 }}
|
||||
>
|
||||
{/* Drag handle - иконка для перетаскивания */}
|
||||
<div
|
||||
{...provided.dragHandleProps}
|
||||
style={{
|
||||
cursor: "grab",
|
||||
padding: "4px 8px 4px 0",
|
||||
color: "#8c8c8c",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
transition: "color 0.2s ease",
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.color = "#1890ff";
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.color = "#8c8c8c";
|
||||
}}
|
||||
>
|
||||
<GripVertical size={20} />
|
||||
</div>
|
||||
{/* Drag handle - иконка для перетаскивания (показываем только в режиме перетаскивания) */}
|
||||
{isReordering && (
|
||||
<div
|
||||
{...provided.dragHandleProps}
|
||||
style={{
|
||||
cursor: "grab",
|
||||
padding: "4px 8px 4px 0",
|
||||
color: "#8c8c8c",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
transition: "color 0.2s ease",
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.color = "#1890ff";
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.color = "#8c8c8c";
|
||||
}}
|
||||
>
|
||||
<GripVertical size={20} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Flex vertical gap={10} style={{ flex: 1 }}>
|
||||
<Flex justify="space-between" align="start">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// 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 {
|
||||
List,
|
||||
@@ -74,9 +74,6 @@ export const DraftsList: React.FC = () => {
|
||||
);
|
||||
const [endDate, setEndDate] = useState<dayjs.Dayjs | null>(dayjs());
|
||||
|
||||
const touchStartX = useRef<number>(0);
|
||||
const touchEndX = useRef<number>(0);
|
||||
|
||||
const {
|
||||
data: invoices,
|
||||
isLoading,
|
||||
@@ -181,14 +178,6 @@ export const DraftsList: React.FC = () => {
|
||||
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) =>
|
||||
item.type === "DRAFT" ? item.created_at : item.date_incoming;
|
||||
|
||||
@@ -226,21 +215,6 @@ export const DraftsList: React.FC = () => {
|
||||
return result;
|
||||
}, [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 startIndex = (currentPage - 1) * pageSize;
|
||||
return filteredAndSortedInvoices.slice(startIndex, startIndex + pageSize);
|
||||
@@ -347,11 +321,7 @@ export const DraftsList: React.FC = () => {
|
||||
<Empty description="Нет данных" />
|
||||
) : (
|
||||
<>
|
||||
<div
|
||||
onTouchStart={handleTouchStart}
|
||||
onTouchMove={handleTouchMove}
|
||||
onTouchEnd={handleTouchEnd}
|
||||
>
|
||||
<div>
|
||||
{Object.entries(groupedInvoices).map(([dateKey, items]) => (
|
||||
<div key={dateKey}>
|
||||
<DayDivider date={dateKey} />
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
RestOutlined,
|
||||
PlusOutlined,
|
||||
FileImageOutlined,
|
||||
SwapOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import dayjs from "dayjs";
|
||||
import { api, getStaticUrl } from "../services/api";
|
||||
@@ -50,6 +51,7 @@ export const InvoiceDraftPage: React.FC = () => {
|
||||
const [updatingItems, setUpdatingItems] = useState<Set<string>>(new Set());
|
||||
const [itemsOrder, setItemsOrder] = useState<Record<string, number>>({});
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [isReordering, setIsReordering] = useState(false);
|
||||
|
||||
// Состояние для просмотра фото чека
|
||||
const [previewVisible, setPreviewVisible] = useState(false);
|
||||
@@ -386,7 +388,7 @@ export const InvoiceDraftPage: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Правая часть хедера: Кнопка чека и Кнопка удаления */}
|
||||
{/* Правая часть хедера: Кнопка чека, Кнопка перетаскивания и Кнопка удаления */}
|
||||
<div style={{ display: "flex", gap: 8 }}>
|
||||
{/* Кнопка просмотра чека (только если есть URL) */}
|
||||
{draft.photo_url && (
|
||||
@@ -399,6 +401,16 @@ export const InvoiceDraftPage: React.FC = () => {
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{/* Кнопка переключения режима перетаскивания */}
|
||||
<Button
|
||||
type={isReordering ? "primary" : "default"}
|
||||
icon={<SwapOutlined rotate={90} />}
|
||||
onClick={() => setIsReordering(!isReordering)}
|
||||
size="small"
|
||||
>
|
||||
{isReordering ? "Ок" : ""}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
danger={isCanceled}
|
||||
type={isCanceled ? "primary" : "default"}
|
||||
@@ -549,6 +561,7 @@ export const InvoiceDraftPage: React.FC = () => {
|
||||
onDelete={(itemId) => deleteItemMutation.mutate(itemId)}
|
||||
isUpdating={updatingItems.has(item.id)}
|
||||
recommendations={recommendationsQuery.data || []}
|
||||
isReordering={isReordering}
|
||||
/>
|
||||
))}
|
||||
{provided.placeholder}
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
Spin,
|
||||
message,
|
||||
Tabs,
|
||||
Popconfirm,
|
||||
} from "antd";
|
||||
import {
|
||||
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(() => {
|
||||
@@ -184,6 +196,33 @@ export const SettingsPage: React.FC = () => {
|
||||
>
|
||||
Сохранить настройки
|
||||
</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>
|
||||
);
|
||||
|
||||
|
||||
@@ -221,6 +221,11 @@ export const api = {
|
||||
deleteDraft: async (id: string): Promise<void> => {
|
||||
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