15.12.25 - Этап 3. Детализации --2
All checks were successful
Mirror frontend to backend repo / mirror (push) Successful in 6s
All checks were successful
Mirror frontend to backend repo / mirror (push) Successful in 6s
This commit is contained in:
@@ -1,15 +1,23 @@
|
|||||||
import apiClient from './axios';
|
import apiClient from './axios';
|
||||||
import { ApiResponse, ServerEntity, WorkstationEntity, FiscalEntity, UpdateServerDTO, UpdateWorkstationDTO, UpdateFiscalDTO } from '@/types/api';
|
import {
|
||||||
|
ApiResponse,
|
||||||
|
ServerDetailDTO,
|
||||||
|
WorkstationDetailDTO,
|
||||||
|
FiscalDetailDTO,
|
||||||
|
UpdateServerPayload,
|
||||||
|
UpdateWorkstationPayload,
|
||||||
|
UpdateFiscalPayload
|
||||||
|
} from '@/types/api';
|
||||||
|
|
||||||
export const equipmentApi = {
|
export const equipmentApi = {
|
||||||
// --- Servers ---
|
// --- Servers ---
|
||||||
getServer: async (uuid: string) => {
|
getServer: async (uuid: string) => {
|
||||||
const response = await apiClient.get<ApiResponse<ServerEntity>>(`/servers/${uuid}`);
|
const response = await apiClient.get<ApiResponse<ServerDetailDTO>>(`/servers/${uuid}`);
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
updateServer: async (uuid: string, data: UpdateServerDTO) => {
|
updateServer: async (uuid: string, data: UpdateServerPayload) => {
|
||||||
const response = await apiClient.put<ApiResponse<ServerEntity>>(`/servers/${uuid}`, data);
|
const response = await apiClient.put<ApiResponse<ServerDetailDTO>>(`/servers/${uuid}`, data);
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -20,23 +28,23 @@ export const equipmentApi = {
|
|||||||
|
|
||||||
// --- Workstations ---
|
// --- Workstations ---
|
||||||
getWorkstation: async (uuid: string) => {
|
getWorkstation: async (uuid: string) => {
|
||||||
const response = await apiClient.get<ApiResponse<WorkstationEntity>>(`/workstations/${uuid}`);
|
const response = await apiClient.get<ApiResponse<WorkstationDetailDTO>>(`/workstations/${uuid}`);
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
updateWorkstation: async (uuid: string, data: UpdateWorkstationDTO) => {
|
updateWorkstation: async (uuid: string, data: UpdateWorkstationPayload) => {
|
||||||
const response = await apiClient.put<ApiResponse<WorkstationEntity>>(`/workstations/${uuid}`, data);
|
const response = await apiClient.put<ApiResponse<WorkstationDetailDTO>>(`/workstations/${uuid}`, data);
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
// --- Fiscals ---
|
// --- Fiscals ---
|
||||||
getFiscal: async (uuid: string) => {
|
getFiscal: async (uuid: string) => {
|
||||||
const response = await apiClient.get<ApiResponse<FiscalEntity>>(`/fiscals/${uuid}`);
|
const response = await apiClient.get<ApiResponse<FiscalDetailDTO>>(`/fiscals/${uuid}`);
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
updateFiscal: async (uuid: string, data: UpdateFiscalDTO) => {
|
updateFiscal: async (uuid: string, data: UpdateFiscalPayload) => {
|
||||||
const response = await apiClient.put<ApiResponse<FiscalEntity>>(`/fiscals/${uuid}`, data);
|
const response = await apiClient.put<ApiResponse<FiscalDetailDTO>>(`/fiscals/${uuid}`, data);
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { Card, Descriptions, Button, Space, Typography, Spin, Badge, Modal, Form, Input, message } from 'antd';
|
import { Card, Descriptions, Button, Space, Typography, Spin, Badge, Modal, Form, Input, message, Table } from 'antd';
|
||||||
import { ArrowLeftOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
|
import { ArrowLeftOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
|
||||||
import { equipmentApi } from '@/api/equipment';
|
import { equipmentApi } from '@/api/equipment';
|
||||||
import { getEntityIcon, getStatusColor } from '@/utils/mappers';
|
import { getEntityIcon, getStatusColor } from '@/utils/mappers';
|
||||||
import { formatRnm } from '@/utils/formatters';
|
import { formatRnm } from '@/utils/formatters';
|
||||||
import { UpdateFiscalDTO } from '@/types/api';
|
import { UpdateFiscalPayload } from '@/types/api';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
const { Title, Text } = Typography;
|
const { Title, Text } = Typography;
|
||||||
@@ -25,7 +25,7 @@ const FiscalDetails: React.FC = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const updateMutation = useMutation({
|
const updateMutation = useMutation({
|
||||||
mutationFn: (values: UpdateFiscalDTO) => equipmentApi.updateFiscal(id!, values),
|
mutationFn: (values: UpdateFiscalPayload) => equipmentApi.updateFiscal(id!, values),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
message.success('Данные обновлены');
|
message.success('Данные обновлены');
|
||||||
queryClient.invalidateQueries({ queryKey: ['fiscal', id] });
|
queryClient.invalidateQueries({ queryKey: ['fiscal', id] });
|
||||||
@@ -39,7 +39,6 @@ const FiscalDetails: React.FC = () => {
|
|||||||
|
|
||||||
const fiscal = fiscalRes.data;
|
const fiscal = fiscalRes.data;
|
||||||
|
|
||||||
// Логика цвета даты окончания ФН
|
|
||||||
const getFnDateColor = (dateStr?: string) => {
|
const getFnDateColor = (dateStr?: string) => {
|
||||||
if (!dateStr) return undefined;
|
if (!dateStr) return undefined;
|
||||||
const diff = dayjs(dateStr).diff(dayjs(), 'day');
|
const diff = dayjs(dateStr).diff(dayjs(), 'day');
|
||||||
@@ -50,11 +49,22 @@ const FiscalDetails: React.FC = () => {
|
|||||||
|
|
||||||
const handleEdit = () => {
|
const handleEdit = () => {
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
description: fiscal.description,
|
description: fiscal.Description,
|
||||||
});
|
});
|
||||||
setIsEditModalOpen(true);
|
setIsEditModalOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Преобразуем лицензии из объекта в массив для таблицы
|
||||||
|
const licensesData = fiscal.Licenses
|
||||||
|
? Object.entries(fiscal.Licenses).map(([id, data]) => ({ id, ...data }))
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const licenseColumns = [
|
||||||
|
{ title: 'ID', dataIndex: 'id', width: 60 },
|
||||||
|
{ title: 'Название', dataIndex: 'name' },
|
||||||
|
{ title: 'До', dataIndex: 'dateUntil', render: (d: string) => d ? d.split(' ')[0] : '-' },
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div style={{ marginBottom: 16, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
<div style={{ marginBottom: 16, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||||
@@ -63,11 +73,11 @@ const FiscalDetails: React.FC = () => {
|
|||||||
<Space>
|
<Space>
|
||||||
<div style={{ fontSize: 24, color: '#1890ff' }}>{getEntityIcon('FiscalRegister')}</div>
|
<div style={{ fontSize: 24, color: '#1890ff' }}>{getEntityIcon('FiscalRegister')}</div>
|
||||||
<div>
|
<div>
|
||||||
<Title level={4} style={{ margin: 0 }}>{fiscal.model_kkt || 'ККТ'}</Title>
|
<Title level={4} style={{ margin: 0 }}>{fiscal.ModelKKT || 'ККТ'}</Title>
|
||||||
<Text type="secondary">{fiscal.serial_number}</Text>
|
<Text type="secondary">{fiscal.FRSerialNumber}</Text>
|
||||||
</div>
|
</div>
|
||||||
</Space>
|
</Space>
|
||||||
<Badge status={getStatusColor(fiscal.health_status)} text={fiscal.health_status} />
|
<Badge status={getStatusColor(fiscal.HealthStatus)} text={fiscal.HealthStatus} />
|
||||||
</Space>
|
</Space>
|
||||||
|
|
||||||
<Space>
|
<Space>
|
||||||
@@ -78,24 +88,22 @@ const FiscalDetails: React.FC = () => {
|
|||||||
|
|
||||||
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
|
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
|
||||||
|
|
||||||
{/* Main Info */}
|
|
||||||
<Card title="Информация о ККТ" className="glass-panel" size="small">
|
<Card title="Информация о ККТ" className="glass-panel" size="small">
|
||||||
<Descriptions bordered column={2}>
|
<Descriptions bordered column={2}>
|
||||||
<Descriptions.Item label="РНМ">
|
<Descriptions.Item label="РНМ">
|
||||||
<Text code>{formatRnm(fiscal.rn_kkt)}</Text>
|
<Text code>{formatRnm(fiscal.RNKKT)}</Text>
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label="Заводской номер">{fiscal.serial_number}</Descriptions.Item>
|
<Descriptions.Item label="Заводской номер">{fiscal.FRSerialNumber}</Descriptions.Item>
|
||||||
<Descriptions.Item label="Модель">{fiscal.model_kkt}</Descriptions.Item>
|
<Descriptions.Item label="Модель">{fiscal.ModelKKT}</Descriptions.Item>
|
||||||
<Descriptions.Item label="Описание">{fiscal.description || '-'}</Descriptions.Item>
|
<Descriptions.Item label="Описание">{fiscal.Description || '-'}</Descriptions.Item>
|
||||||
</Descriptions>
|
</Descriptions>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* FN Info */}
|
|
||||||
<Card title="Фискальный Накопитель" className="glass-panel" size="small">
|
<Card title="Фискальный Накопитель" className="glass-panel" size="small">
|
||||||
<Descriptions bordered column={2}>
|
<Descriptions bordered column={2}>
|
||||||
<Descriptions.Item label="Номер ФН">{fiscal.fn_number || '-'}</Descriptions.Item>
|
<Descriptions.Item label="Номер ФН">{fiscal.FNNumber || '-'}</Descriptions.Item>
|
||||||
<Descriptions.Item label="Дата регистрации">
|
<Descriptions.Item label="Дата регистрации">
|
||||||
{fiscal.fn_registration_date ? dayjs(fiscal.fn_registration_date).format('DD.MM.YYYY') : '-'}
|
{fiscal.kkt_reg_date ? dayjs(fiscal.kkt_reg_date).format('DD.MM.YYYY') : '-'}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label="Дата окончания">
|
<Descriptions.Item label="Дата окончания">
|
||||||
<Text strong style={{ color: getFnDateColor(fiscal.fn_expire_date) }}>
|
<Text strong style={{ color: getFnDateColor(fiscal.fn_expire_date) }}>
|
||||||
@@ -105,23 +113,33 @@ const FiscalDetails: React.FC = () => {
|
|||||||
</Descriptions>
|
</Descriptions>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Firmware Info */}
|
|
||||||
<Card title="Прошивки и ПО" className="glass-panel" size="small">
|
<Card title="Прошивки и ПО" className="glass-panel" size="small">
|
||||||
<Descriptions bordered column={3}>
|
<Descriptions bordered column={3}>
|
||||||
<Descriptions.Item label="Прошивка ФР">{fiscal.fr_firmware || '-'}</Descriptions.Item>
|
<Descriptions.Item label="Прошивка ФР">{fiscal.FRFirmware || '-'}</Descriptions.Item>
|
||||||
<Descriptions.Item label="Загрузчик">{fiscal.fr_downloader || '-'}</Descriptions.Item>
|
<Descriptions.Item label="Загрузчик">{fiscal.FRDownloader || '-'}</Descriptions.Item>
|
||||||
<Descriptions.Item label="Драйвер">{fiscal.driver_version || '-'}</Descriptions.Item>
|
<Descriptions.Item label="Драйвер">{fiscal.DriverVersion || '-'}</Descriptions.Item>
|
||||||
</Descriptions>
|
</Descriptions>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Legal Info */}
|
|
||||||
<Card title="Юридическое лицо" className="glass-panel" size="small">
|
<Card title="Юридическое лицо" className="glass-panel" size="small">
|
||||||
<Descriptions bordered column={1}>
|
<Descriptions bordered column={1}>
|
||||||
<Descriptions.Item label="Организация">{fiscal.organization_name || '-'}</Descriptions.Item>
|
<Descriptions.Item label="Организация">{fiscal.LegalName || '-'}</Descriptions.Item>
|
||||||
<Descriptions.Item label="ИНН">{fiscal.inn || '-'}</Descriptions.Item>
|
<Descriptions.Item label="ИНН">{fiscal.INN || '-'}</Descriptions.Item>
|
||||||
<Descriptions.Item label="Адрес установки">{fiscal.address || '-'}</Descriptions.Item>
|
<Descriptions.Item label="Адрес установки">{fiscal.address || '-'}</Descriptions.Item>
|
||||||
</Descriptions>
|
</Descriptions>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{licensesData.length > 0 && (
|
||||||
|
<Card title="Лицензии ККТ" className="glass-panel" size="small">
|
||||||
|
<Table
|
||||||
|
dataSource={licensesData}
|
||||||
|
columns={licenseColumns}
|
||||||
|
rowKey="id"
|
||||||
|
pagination={false}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { ArrowLeftOutlined, EditOutlined, SyncOutlined, DeleteOutlined } from '@
|
|||||||
import { equipmentApi } from '@/api/equipment';
|
import { equipmentApi } from '@/api/equipment';
|
||||||
import { getEntityIcon, getStatusColor } from '@/utils/mappers';
|
import { getEntityIcon, getStatusColor } from '@/utils/mappers';
|
||||||
import { formatDate } from '@/utils/formatters';
|
import { formatDate } from '@/utils/formatters';
|
||||||
import { UpdateServerDTO } from '@/types/api';
|
import { UpdateServerPayload } from '@/types/api';
|
||||||
|
|
||||||
const { Title, Text, Paragraph } = Typography;
|
const { Title, Text, Paragraph } = Typography;
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ const ServerDetails: React.FC = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const updateMutation = useMutation({
|
const updateMutation = useMutation({
|
||||||
mutationFn: (values: UpdateServerDTO) => equipmentApi.updateServer(id!, values),
|
mutationFn: (values: UpdateServerPayload) => equipmentApi.updateServer(id!, values),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
message.success('Данные сервера обновлены');
|
message.success('Данные сервера обновлены');
|
||||||
queryClient.invalidateQueries({ queryKey: ['server', id] });
|
queryClient.invalidateQueries({ queryKey: ['server', id] });
|
||||||
@@ -46,12 +46,13 @@ const ServerDetails: React.FC = () => {
|
|||||||
const server = serverRes.data;
|
const server = serverRes.data;
|
||||||
|
|
||||||
const handleEdit = () => {
|
const handleEdit = () => {
|
||||||
|
// Маппинг для формы (PascalCase -> snake_case payload)
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
device_name: server.device_name,
|
device_name: server.DeviceName,
|
||||||
ip: server.ip,
|
ip: server.IP,
|
||||||
anydesk: server.anydesk,
|
anydesk: server.Anydesk,
|
||||||
teamviewer: server.teamviewer,
|
teamviewer: server.Teamviewer,
|
||||||
description: server.description,
|
description: server.Description,
|
||||||
});
|
});
|
||||||
setIsEditModalOpen(true);
|
setIsEditModalOpen(true);
|
||||||
};
|
};
|
||||||
@@ -65,12 +66,12 @@ const ServerDetails: React.FC = () => {
|
|||||||
<Space>
|
<Space>
|
||||||
<div style={{ fontSize: 24, color: '#1890ff' }}>{getEntityIcon('Server')}</div>
|
<div style={{ fontSize: 24, color: '#1890ff' }}>{getEntityIcon('Server')}</div>
|
||||||
<div>
|
<div>
|
||||||
<Title level={4} style={{ margin: 0 }}>{server.device_name || server.server_name || 'Server'}</Title>
|
<Title level={4} style={{ margin: 0 }}>{server.DeviceName || server.ServerName || 'Server'}</Title>
|
||||||
<Text type="secondary">{server.uuid}</Text>
|
<Text type="secondary">{server.ID}</Text>
|
||||||
</div>
|
</div>
|
||||||
</Space>
|
</Space>
|
||||||
<Tag color={getStatusColor(server.operational_status) === 'success' ? 'green' : 'red'}>
|
<Tag color={getStatusColor(server.Status) === 'success' ? 'green' : 'red'}>
|
||||||
{server.operational_status?.toUpperCase()}
|
{server.Status?.toUpperCase()}
|
||||||
</Tag>
|
</Tag>
|
||||||
</Space>
|
</Space>
|
||||||
|
|
||||||
@@ -93,23 +94,23 @@ const ServerDetails: React.FC = () => {
|
|||||||
<Card title="Основная информация" className="glass-panel" size="small">
|
<Card title="Основная информация" className="glass-panel" size="small">
|
||||||
<Descriptions bordered column={2}>
|
<Descriptions bordered column={2}>
|
||||||
<Descriptions.Item label="IP Адрес">
|
<Descriptions.Item label="IP Адрес">
|
||||||
<Paragraph copyable={{ text: server.ip }} style={{ margin: 0 }}>{server.ip || '-'}</Paragraph>
|
<Paragraph copyable={{ text: server.IP }} style={{ margin: 0 }}>{server.IP || '-'}</Paragraph>
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label="Health Status">
|
<Descriptions.Item label="Health Status">
|
||||||
<Badge status={getStatusColor(server.health_status)} text={server.health_status} />
|
<Badge status={getStatusColor(server.HealthStatus)} text={server.HealthStatus} />
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label="Unique ID">{server.unique_id || '-'}</Descriptions.Item>
|
<Descriptions.Item label="Unique ID">{server.UniqueID || '-'}</Descriptions.Item>
|
||||||
<Descriptions.Item label="CRM ID">{server.crm_id || '-'}</Descriptions.Item>
|
<Descriptions.Item label="CRM ID">{server.CRMid || '-'}</Descriptions.Item>
|
||||||
<Descriptions.Item label="Описание" span={2}>{server.description || '-'}</Descriptions.Item>
|
<Descriptions.Item label="Описание" span={2}>{server.Description || '-'}</Descriptions.Item>
|
||||||
</Descriptions>
|
</Descriptions>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Software Info */}
|
{/* Software Info */}
|
||||||
<Card title="Программное обеспечение" className="glass-panel" size="small">
|
<Card title="Программное обеспечение" className="glass-panel" size="small">
|
||||||
<Descriptions bordered column={2}>
|
<Descriptions bordered column={2}>
|
||||||
<Descriptions.Item label="Версия сервера">{server.server_version || '-'}</Descriptions.Item>
|
<Descriptions.Item label="Версия сервера">{server.ServerVersion || '-'}</Descriptions.Item>
|
||||||
<Descriptions.Item label="Редакция">{server.server_edition || '-'}</Descriptions.Item>
|
<Descriptions.Item label="Редакция">{server.ServerEdition || '-'}</Descriptions.Item>
|
||||||
<Descriptions.Item label="Посл. опрос">{formatDate(server.last_polled_at)}</Descriptions.Item>
|
<Descriptions.Item label="Посл. опрос">{formatDate(server.LastPolledAt)}</Descriptions.Item>
|
||||||
</Descriptions>
|
</Descriptions>
|
||||||
</Card>
|
</Card>
|
||||||
</Space>
|
</Space>
|
||||||
@@ -119,17 +120,17 @@ const ServerDetails: React.FC = () => {
|
|||||||
<Card title="Удаленный доступ" className="glass-panel" size="small">
|
<Card title="Удаленный доступ" className="glass-panel" size="small">
|
||||||
<Descriptions column={1} layout="vertical">
|
<Descriptions column={1} layout="vertical">
|
||||||
<Descriptions.Item label="AnyDesk">
|
<Descriptions.Item label="AnyDesk">
|
||||||
{server.anydesk ? <Paragraph copyable>{server.anydesk}</Paragraph> : <Text type="secondary">-</Text>}
|
{server.Anydesk ? <Paragraph copyable>{server.Anydesk}</Paragraph> : <Text type="secondary">-</Text>}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label="TeamViewer">
|
<Descriptions.Item label="TeamViewer">
|
||||||
{server.teamviewer ? <Paragraph copyable>{server.teamviewer}</Paragraph> : <Text type="secondary">-</Text>}
|
{server.Teamviewer ? <Paragraph copyable>{server.Teamviewer}</Paragraph> : <Text type="secondary">-</Text>}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label="LiteManager">
|
<Descriptions.Item label="RDP">
|
||||||
{server.litemanager ? <Paragraph copyable>{server.litemanager}</Paragraph> : <Text type="secondary">-</Text>}
|
{server.RDP ? <Paragraph copyable>{server.RDP}</Paragraph> : <Text type="secondary">-</Text>}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
{server.partners_link && (
|
{server.CabinetLink && (
|
||||||
<Descriptions.Item label="Кабинет">
|
<Descriptions.Item label="Кабинет">
|
||||||
<Button type="link" href={server.partners_link} target="_blank" style={{ padding: 0 }}>
|
<Button type="link" href={server.CabinetLink} target="_blank" style={{ padding: 0 }}>
|
||||||
Перейти в кабинет дилера
|
Перейти в кабинет дилера
|
||||||
</Button>
|
</Button>
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { Card, Descriptions, Button, Space, Typography, Spin, Badge, Modal, Form
|
|||||||
import { ArrowLeftOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
|
import { ArrowLeftOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
|
||||||
import { equipmentApi } from '@/api/equipment';
|
import { equipmentApi } from '@/api/equipment';
|
||||||
import { getEntityIcon, getStatusColor } from '@/utils/mappers';
|
import { getEntityIcon, getStatusColor } from '@/utils/mappers';
|
||||||
import { UpdateWorkstationDTO } from '@/types/api';
|
import { UpdateWorkstationPayload } from '@/types/api';
|
||||||
|
|
||||||
const { Title, Text, Paragraph } = Typography;
|
const { Title, Text, Paragraph } = Typography;
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ const WorkstationDetails: React.FC = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const updateMutation = useMutation({
|
const updateMutation = useMutation({
|
||||||
mutationFn: (values: UpdateWorkstationDTO) => equipmentApi.updateWorkstation(id!, values),
|
mutationFn: (values: UpdateWorkstationPayload) => equipmentApi.updateWorkstation(id!, values),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
message.success('Данные обновлены');
|
message.success('Данные обновлены');
|
||||||
queryClient.invalidateQueries({ queryKey: ['workstation', id] });
|
queryClient.invalidateQueries({ queryKey: ['workstation', id] });
|
||||||
@@ -39,10 +39,10 @@ const WorkstationDetails: React.FC = () => {
|
|||||||
|
|
||||||
const handleEdit = () => {
|
const handleEdit = () => {
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
device_name: ws.device_name,
|
device_name: ws.DeviceName,
|
||||||
anydesk: ws.anydesk,
|
anydesk: ws.Anydesk,
|
||||||
teamviewer: ws.teamviewer,
|
teamviewer: ws.Teamviewer,
|
||||||
description: ws.description,
|
description: ws.Description,
|
||||||
});
|
});
|
||||||
setIsEditModalOpen(true);
|
setIsEditModalOpen(true);
|
||||||
};
|
};
|
||||||
@@ -55,11 +55,11 @@ const WorkstationDetails: React.FC = () => {
|
|||||||
<Space>
|
<Space>
|
||||||
<div style={{ fontSize: 24, color: '#1890ff' }}>{getEntityIcon('Workstation')}</div>
|
<div style={{ fontSize: 24, color: '#1890ff' }}>{getEntityIcon('Workstation')}</div>
|
||||||
<div>
|
<div>
|
||||||
<Title level={4} style={{ margin: 0 }}>{ws.device_name || 'Workstation'}</Title>
|
<Title level={4} style={{ margin: 0 }}>{ws.DeviceName || 'Workstation'}</Title>
|
||||||
<Text type="secondary">{ws.uuid}</Text>
|
<Text type="secondary">{ws.ID}</Text>
|
||||||
</div>
|
</div>
|
||||||
</Space>
|
</Space>
|
||||||
<Badge status={getStatusColor(ws.health_status)} text={ws.health_status} />
|
<Badge status={getStatusColor(ws.HealthStatus)} text={ws.HealthStatus} />
|
||||||
</Space>
|
</Space>
|
||||||
|
|
||||||
<Space>
|
<Space>
|
||||||
@@ -71,16 +71,16 @@ const WorkstationDetails: React.FC = () => {
|
|||||||
<Card title="Детали рабочей станции" className="glass-panel" size="small">
|
<Card title="Детали рабочей станции" className="glass-panel" size="small">
|
||||||
<Descriptions bordered column={1}>
|
<Descriptions bordered column={1}>
|
||||||
<Descriptions.Item label="Описание">
|
<Descriptions.Item label="Описание">
|
||||||
{ws.description || '-'}
|
{ws.Description || '-'}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label="AnyDesk">
|
<Descriptions.Item label="AnyDesk">
|
||||||
{ws.anydesk ? <Paragraph copyable>{ws.anydesk}</Paragraph> : '-'}
|
{ws.Anydesk ? <Paragraph copyable>{ws.Anydesk}</Paragraph> : '-'}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label="TeamViewer">
|
<Descriptions.Item label="TeamViewer">
|
||||||
{ws.teamviewer ? <Paragraph copyable>{ws.teamviewer}</Paragraph> : '-'}
|
{ws.Teamviewer ? <Paragraph copyable>{ws.Teamviewer}</Paragraph> : '-'}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label="LiteManager">
|
<Descriptions.Item label="LiteManager">
|
||||||
{ws.litemanager ? <Paragraph copyable>{ws.litemanager}</Paragraph> : '-'}
|
{ws.Litemanager ? <Paragraph copyable>{ws.Litemanager}</Paragraph> : '-'}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
</Descriptions>
|
</Descriptions>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
107
src/types/api.ts
107
src/types/api.ts
@@ -206,4 +206,111 @@ export interface UpdateWorkstationDTO {
|
|||||||
export interface UpdateFiscalDTO {
|
export interface UpdateFiscalDTO {
|
||||||
description?: string;
|
description?: string;
|
||||||
// ККТ поля обычно read-only, т.к. приходят из железа, но description можно править
|
// ККТ поля обычно read-only, т.к. приходят из железа, но description можно править
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Detail DTOs (Strictly matching Backend JSON) ---
|
||||||
|
|
||||||
|
export interface LicensesDict {
|
||||||
|
[key: string]: {
|
||||||
|
name: string;
|
||||||
|
dateFrom: string;
|
||||||
|
dateUntil: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FiscalDetailDTO {
|
||||||
|
ID: string;
|
||||||
|
ModelKKT?: string;
|
||||||
|
RNKKT?: string;
|
||||||
|
LegalName?: string;
|
||||||
|
INN?: string;
|
||||||
|
FRSerialNumber?: string;
|
||||||
|
FNNumber?: string;
|
||||||
|
|
||||||
|
// Внимание: смешанный регистр в JSON
|
||||||
|
kkt_reg_date?: string;
|
||||||
|
fn_expire_date?: string;
|
||||||
|
|
||||||
|
FRFirmware?: string;
|
||||||
|
FRDownloader?: string;
|
||||||
|
DriverVersion?: string;
|
||||||
|
|
||||||
|
HealthStatus?: 'ok' | 'attention_required' | 'locked';
|
||||||
|
|
||||||
|
// Внимание: lowercase в JSON
|
||||||
|
address?: string;
|
||||||
|
Description?: string;
|
||||||
|
|
||||||
|
Licenses?: LicensesDict;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WorkstationDetailDTO {
|
||||||
|
ID: string;
|
||||||
|
DeviceName?: string;
|
||||||
|
Teamviewer?: string;
|
||||||
|
Anydesk?: string;
|
||||||
|
Litemanager?: string;
|
||||||
|
Description?: string;
|
||||||
|
HealthStatus?: 'ok' | 'attention_required' | 'locked';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServerDetailDTO {
|
||||||
|
ID: string;
|
||||||
|
UniqueID?: string;
|
||||||
|
IP?: string;
|
||||||
|
DeviceName?: string;
|
||||||
|
ServerName?: string;
|
||||||
|
ServerVersion?: string;
|
||||||
|
ServerEdition?: string;
|
||||||
|
|
||||||
|
// PascalCase
|
||||||
|
LastPolledAt?: string;
|
||||||
|
Status?: 'active' | 'offline' | 'unknown'; // Operational Status
|
||||||
|
HealthStatus?: 'ok' | 'attention_required' | 'locked';
|
||||||
|
|
||||||
|
CabinetLink?: string;
|
||||||
|
CRMid?: string;
|
||||||
|
|
||||||
|
RDP?: string;
|
||||||
|
Teamviewer?: string;
|
||||||
|
Anydesk?: string;
|
||||||
|
Litemanager?: string;
|
||||||
|
Description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... (предыдущие TaskDTO и прочее остаются)
|
||||||
|
export interface ApiResponse<T> {
|
||||||
|
status: 'success' | 'error';
|
||||||
|
data: T;
|
||||||
|
meta?: PaginationMeta;
|
||||||
|
error?: {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Необходимо убедиться, что PaginationMeta и другие типы на месте
|
||||||
|
export interface PaginationMeta {
|
||||||
|
total: number;
|
||||||
|
limit: number;
|
||||||
|
offset: number;
|
||||||
|
has_next: boolean;
|
||||||
|
has_prev: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateServerPayload {
|
||||||
|
device_name?: string;
|
||||||
|
ip?: string;
|
||||||
|
anydesk?: string;
|
||||||
|
teamviewer?: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateWorkstationPayload {
|
||||||
|
device_name?: string;
|
||||||
|
anydesk?: string;
|
||||||
|
teamviewer?: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateFiscalPayload {
|
||||||
|
description?: string;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user