15.12.25 - Этап 3. Детализации --2
All checks were successful
Mirror frontend to backend repo / mirror (push) Successful in 6s

This commit is contained in:
2025-12-15 08:17:22 +03:00
parent 33e579b402
commit 6753bb02b8
5 changed files with 205 additions and 71 deletions

View File

@@ -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;
}, },
}; };

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;
} }