добавил пользователей для сервера и роли

добавил инвайт-ссылки с ролью оператор для сервера
добавил супер-админку для смены владельцев
добавил уведомления о смене ролей на серверах
добавил модалку для фото прям в черновике
добавил UI для редактирования прав
This commit is contained in:
2025-12-23 13:06:06 +03:00
parent 9441579a34
commit b4ce819931
21 changed files with 9244 additions and 418 deletions

View File

@@ -16,6 +16,7 @@ import {
Affix,
Modal,
Tag,
Image,
} from "antd";
import {
ArrowLeftOutlined,
@@ -24,9 +25,10 @@ import {
ExclamationCircleFilled,
RestOutlined,
PlusOutlined,
FileImageOutlined,
} from "@ant-design/icons";
import dayjs from "dayjs";
import { api } from "../services/api";
import { api, getStaticUrl } from "../services/api";
import { DraftItemRow } from "../components/invoices/DraftItemRow";
import type {
UpdateDraftItemRequest,
@@ -45,6 +47,9 @@ export const InvoiceDraftPage: React.FC = () => {
const [updatingItems, setUpdatingItems] = useState<Set<string>>(new Set());
// Состояние для просмотра фото чека
const [previewVisible, setPreviewVisible] = useState(false);
// --- ЗАПРОСЫ ---
const dictQuery = useQuery({
@@ -95,20 +100,17 @@ export const InvoiceDraftPage: React.FC = () => {
},
});
// ДОБАВЛЕНО: Добавление строки
const addItemMutation = useMutation({
mutationFn: () => api.addDraftItem(id!),
onSuccess: () => {
message.success("Строка добавлена");
queryClient.invalidateQueries({ queryKey: ["draft", id] });
// Можно сделать скролл вниз, но пока оставим как есть
},
onError: () => {
message.error("Ошибка создания строки");
},
});
// ДОБАВЛЕНО: Удаление строки
const deleteItemMutation = useMutation({
mutationFn: (itemId: string) => api.deleteDraftItem(id!, itemId),
onSuccess: () => {
@@ -293,16 +295,30 @@ export const InvoiceDraftPage: React.FC = () => {
</div>
</div>
<Button
danger={isCanceled}
type={isCanceled ? "primary" : "default"}
icon={isCanceled ? <DeleteOutlined /> : <RestOutlined />}
onClick={handleDelete}
loading={deleteDraftMutation.isPending}
size="small"
>
{isCanceled ? "Удалить" : "Отмена"}
</Button>
{/* Правая часть хедера: Кнопка чека и Кнопка удаления */}
<div style={{ display: "flex", gap: 8 }}>
{/* Кнопка просмотра чека (только если есть URL) */}
{draft.photo_url && (
<Button
icon={<FileImageOutlined />}
onClick={() => setPreviewVisible(true)}
size="small"
>
Чек
</Button>
)}
<Button
danger={isCanceled}
type={isCanceled ? "primary" : "default"}
icon={isCanceled ? <DeleteOutlined /> : <RestOutlined />}
onClick={handleDelete}
loading={deleteDraftMutation.isPending}
size="small"
>
{isCanceled ? "Удалить" : "Отмена"}
</Button>
</div>
</div>
{/* Form: Склады и Поставщики */}
@@ -422,7 +438,7 @@ export const InvoiceDraftPage: React.FC = () => {
type="dashed"
block
icon={<PlusOutlined />}
style={{ marginTop: 12, marginBottom: 80, height: 48 }} // Увеличенный margin bottom для Affix
style={{ marginTop: 12, marginBottom: 80, height: 48 }}
onClick={() => addItemMutation.mutate()}
loading={addItemMutation.isPending}
disabled={isCanceled}
@@ -476,6 +492,22 @@ export const InvoiceDraftPage: React.FC = () => {
</Button>
</div>
</Affix>
{/* Скрытый компонент для просмотра изображения */}
{draft.photo_url && (
<div style={{ display: "none" }}>
<Image.PreviewGroup
preview={{
visible: previewVisible,
onVisibleChange: (vis) => setPreviewVisible(vis),
movable: true,
scaleStep: 0.5,
}}
>
<Image src={getStaticUrl(draft.photo_url)} />
</Image.PreviewGroup>
</div>
)}
</div>
);
};