Merge pull request 'модалка для выбора всп' (#11) from AURORA-894 into test
Reviewed-on: #11 Reviewed-by: Veronika <veronika.amilonn@gmail.com>
This commit is contained in:
commit
3c1722293c
@ -21,4 +21,7 @@ export const DictVspApi = {
|
|||||||
responseType: "blob",
|
responseType: "blob",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
getDropdownVsp(params){
|
||||||
|
return api.get(`/dict/info/dropdown`, params).then((r) => r.data);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
139
web/src/components/RealtimeTable/Modals/SelectVspModal.jsx
Normal file
139
web/src/components/RealtimeTable/Modals/SelectVspModal.jsx
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Divider,
|
||||||
|
FormControl,
|
||||||
|
FormLabel,
|
||||||
|
IconButton,
|
||||||
|
Stack,
|
||||||
|
Select,
|
||||||
|
MenuItem,
|
||||||
|
} from '@mui/material';
|
||||||
|
import {
|
||||||
|
ModalBackdrop,
|
||||||
|
ModalContainer,
|
||||||
|
HeaderContainer,
|
||||||
|
ModalTitle,
|
||||||
|
} from '../../common/Modal/ModalStyled';
|
||||||
|
import { Cancel } from '../../common/icons/icons';
|
||||||
|
import { DictVspApi } from '../../../api/dict-vsp';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
export const SelectVspModal = ({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
onSelect,
|
||||||
|
formId,
|
||||||
|
title = 'Выбор ВСП',
|
||||||
|
isSaving = false,
|
||||||
|
}) => {
|
||||||
|
const [selectedValue, setSelectedValue] = useState('');
|
||||||
|
const [vspOptions, setVspOption] = useState([])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (formId === null) return;
|
||||||
|
DictVspApi.getDropdownVsp({ form_id: formId }).then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
setVspOption(data.result)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
toast.error('Ошибка получения ВСП')
|
||||||
|
})
|
||||||
|
}, [formId])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) {
|
||||||
|
setSelectedValue('');
|
||||||
|
}
|
||||||
|
}, [isOpen])
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
setSelectedValue('');
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBackdropClick = (e) => {
|
||||||
|
if (e.target === e.currentTarget) {
|
||||||
|
handleCancel();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
onSelect?.(selectedValue);
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const isFormValid = selectedValue;
|
||||||
|
|
||||||
|
if (!isOpen) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalBackdrop isOpen={isOpen} onClick={handleBackdropClick}>
|
||||||
|
<ModalContainer height={'max-content'} >
|
||||||
|
<HeaderContainer>
|
||||||
|
<Stack direction="row" spacing={2} sx={{ alignItems: 'center' }}>
|
||||||
|
<ModalTitle>{title}</ModalTitle>
|
||||||
|
</Stack>
|
||||||
|
<IconButton size="small" onClick={handleCancel}>
|
||||||
|
<Cancel />
|
||||||
|
</IconButton>
|
||||||
|
</HeaderContainer>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Divider sx={{ marginBottom: '0.75rem' }} />
|
||||||
|
|
||||||
|
{/* Выбор ВСП */}
|
||||||
|
<Stack sx={{ marginBottom: '0.75rem', width: '100%' }}>
|
||||||
|
<FormControl fullWidth>
|
||||||
|
<Select
|
||||||
|
value={selectedValue}
|
||||||
|
onChange={(e) => setSelectedValue(e.target.value)}
|
||||||
|
displayEmpty
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled>
|
||||||
|
Выберите ВСП
|
||||||
|
</MenuItem>
|
||||||
|
{vspOptions.map((option) => (
|
||||||
|
<MenuItem key={option.id} value={option.id}>
|
||||||
|
{option.registration_number}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
spacing={0.75}
|
||||||
|
sx={{
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
marginTop: '1.5rem',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
color="default"
|
||||||
|
onClick={handleCancel}
|
||||||
|
style={{ height: '2.5rem' }}
|
||||||
|
disabled={isSaving}
|
||||||
|
>
|
||||||
|
Отмена
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={handleSave}
|
||||||
|
style={{ height: '2.5rem' }}
|
||||||
|
disabled={!isFormValid || isSaving}
|
||||||
|
loading={isSaving}
|
||||||
|
>
|
||||||
|
Выбрать
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
</ModalContainer>
|
||||||
|
</ModalBackdrop>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -25,6 +25,9 @@ import {
|
|||||||
import { useRealtime } from './contexts/RealtimeContext';
|
import { useRealtime } from './contexts/RealtimeContext';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { CircularProgress } from '@mui/material';
|
import { CircularProgress } from '@mui/material';
|
||||||
|
import { additionVspRowTable } from './constants/addingRowConfig';
|
||||||
|
import { SelectVspModal } from './Modals/selectVspModal';
|
||||||
|
import { getRowId } from './utils/rowUtils';
|
||||||
|
|
||||||
const RealtimeTable = ({ formType, formId, sheetName, direction }) => {
|
const RealtimeTable = ({ formType, formId, sheetName, direction }) => {
|
||||||
const { data, setData, isLoading: isTableLoading, editingCells: dataEditingCells } = useRealtimeData(formId, sheetName, direction);
|
const { data, setData, isLoading: isTableLoading, editingCells: dataEditingCells } = useRealtimeData(formId, sheetName, direction);
|
||||||
@ -36,6 +39,14 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => {
|
|||||||
const [isPending, startTransition] = useTransition();
|
const [isPending, startTransition] = useTransition();
|
||||||
const [editingCell, setEditingCell] = useState(null);
|
const [editingCell, setEditingCell] = useState(null);
|
||||||
|
|
||||||
|
const [isOpenModalSelectVsp, setIsOpenModalSelectVsp] = useState(false);
|
||||||
|
|
||||||
|
const isVspAdditionRow = useMemo(() => {
|
||||||
|
if (!formType || !sheetName) return false;
|
||||||
|
if (!additionVspRowTable[formType]) return false;
|
||||||
|
return additionVspRowTable[formType].includes(sheetName);
|
||||||
|
}, [formType, sheetName]);
|
||||||
|
|
||||||
const handleGlobalFilterChange = (value) => {
|
const handleGlobalFilterChange = (value) => {
|
||||||
startTransition(() => {
|
startTransition(() => {
|
||||||
setGlobalFilter(value);
|
setGlobalFilter(value);
|
||||||
@ -306,6 +317,14 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => {
|
|||||||
contextAddRow({ expense_item_id: expense_item_id })
|
contextAddRow({ expense_item_id: expense_item_id })
|
||||||
}, [rowSelection, table])
|
}, [rowSelection, table])
|
||||||
|
|
||||||
|
const handleAddVspRow = useCallback((vsp_id) => {
|
||||||
|
contextAddRow({ vsp_id: vsp_id });
|
||||||
|
}, [rowSelection, table]);
|
||||||
|
|
||||||
|
const handleOpenModalSelectVsp = useCallback(() => {
|
||||||
|
setIsOpenModalSelectVsp(true);
|
||||||
|
}, [rowSelection, table])
|
||||||
|
|
||||||
const handleDeleteRow = useCallback(() => {
|
const handleDeleteRow = useCallback(() => {
|
||||||
const allRows = table.getRowModel().rows;
|
const allRows = table.getRowModel().rows;
|
||||||
|
|
||||||
@ -315,8 +334,8 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const row = selectedRows[0];
|
const row = selectedRows[0];
|
||||||
const expense_item_id = row.original.data.line_id;
|
const rowId = getRowId(row);
|
||||||
contextDeleteRow(expense_item_id)
|
contextDeleteRow(rowId);
|
||||||
setRowSelection({});
|
setRowSelection({});
|
||||||
}, [rowSelection, table])
|
}, [rowSelection, table])
|
||||||
|
|
||||||
@ -335,7 +354,7 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => {
|
|||||||
onGlobalFilterChange={handleGlobalFilterChange}
|
onGlobalFilterChange={handleGlobalFilterChange}
|
||||||
sizeMult={sizeMult}
|
sizeMult={sizeMult}
|
||||||
onChangeShowColumnFilters={setShowColumnFilters}
|
onChangeShowColumnFilters={setShowColumnFilters}
|
||||||
onAddRow={handleAddRow}
|
onAddRow={isVspAdditionRow ? handleOpenModalSelectVsp : handleAddRow}
|
||||||
onDeleteRow={handleDeleteRow}
|
onDeleteRow={handleDeleteRow}
|
||||||
isLoadingData={isLoadingData}
|
isLoadingData={isLoadingData}
|
||||||
formId={formId}
|
formId={formId}
|
||||||
@ -396,6 +415,15 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<SelectVspModal
|
||||||
|
isOpen={isOpenModalSelectVsp}
|
||||||
|
onClose={() => setIsOpenModalSelectVsp(false)}
|
||||||
|
formId={formId}
|
||||||
|
onSelect={handleAddVspRow}
|
||||||
|
// vspOptions={}
|
||||||
|
// selectedVSP={}
|
||||||
|
title="Выбор ВСП"
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,3 @@
|
|||||||
|
export const additionVspRowTable = {
|
||||||
|
FORM_2: ["AHR_SECURITY", "AHR_RENT", "AHR_UTILITY"],
|
||||||
|
};
|
||||||
@ -8,6 +8,7 @@ import React, {
|
|||||||
} from "react";
|
} from "react";
|
||||||
import { useWebSocket } from "../hooks/useWebSocket";
|
import { useWebSocket } from "../hooks/useWebSocket";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
|
import { getRowId } from "../utils/rowUtils";
|
||||||
|
|
||||||
const RealtimeContext = createContext(null);
|
const RealtimeContext = createContext(null);
|
||||||
|
|
||||||
@ -33,7 +34,8 @@ export const RealtimeProvider = ({
|
|||||||
const handleMessage = useCallback((data) => {
|
const handleMessage = useCallback((data) => {
|
||||||
// Обработка ошибок
|
// Обработка ошибок
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
toast.error("Server error:", data.error);
|
console.log(data.error.message)
|
||||||
|
toast.error("Server error:" + data.error.message || '');
|
||||||
onErrorRef.current?.(data.error);
|
onErrorRef.current?.(data.error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -48,7 +50,7 @@ export const RealtimeProvider = ({
|
|||||||
|
|
||||||
case "cell_edit_end":
|
case "cell_edit_end":
|
||||||
console.log("Cell edit ended:", data);
|
console.log("Cell edit ended:", data);
|
||||||
if ( onCellEditEndRef.current) {
|
if (onCellEditEndRef.current) {
|
||||||
onCellEditEndRef.current(data);
|
onCellEditEndRef.current(data);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -216,7 +218,7 @@ export const RealtimeProvider = ({
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const lineId = row.original?.data?.line_id || null;
|
const lineId = getRowId(row);
|
||||||
const colId = columnId.slice(5);
|
const colId = columnId.slice(5);
|
||||||
const message = {
|
const message = {
|
||||||
event: "cell_updated",
|
event: "cell_updated",
|
||||||
|
|||||||
@ -2,6 +2,8 @@ import { useState, useEffect, useCallback, useRef } from "react";
|
|||||||
import { FormsSheetApi } from "../../../api/form_sheet";
|
import { FormsSheetApi } from "../../../api/form_sheet";
|
||||||
import { useRealtime } from "../contexts/RealtimeContext";
|
import { useRealtime } from "../contexts/RealtimeContext";
|
||||||
import { deleteRow, insertCell, updateCells } from "../utils/cellUtils";
|
import { deleteRow, insertCell, updateCells } from "../utils/cellUtils";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { isVspNewRow, isVspRow } from "../utils/rowUtils";
|
||||||
|
|
||||||
const useRealtimeData = (formId, sheetName, direction) => {
|
const useRealtimeData = (formId, sheetName, direction) => {
|
||||||
const [data, setData] = useState([]);
|
const [data, setData] = useState([]);
|
||||||
@ -176,11 +178,11 @@ const useRealtimeData = (formId, sheetName, direction) => {
|
|||||||
|
|
||||||
const unsubscribeRowAdds = subscribeToRowAdds((data) => {
|
const unsubscribeRowAdds = subscribeToRowAdds((data) => {
|
||||||
const updates = data.result.data;
|
const updates = data.result.data;
|
||||||
const expense_item_id = data.data.expense_item_id;
|
const expense_item_id = data.data.expense_item_id || null;
|
||||||
const newLineId = data.result.new_line_id;
|
const newLineId = data.result.new_line_id;
|
||||||
const [updatedCells, newRow] = updates.reduce(
|
const [updatedCells, newRow] = updates.reduce(
|
||||||
(res, row) => {
|
(res, row) => {
|
||||||
if (row[3]?.line_id === newLineId) {
|
if (row[3]?.line_id === newLineId || isVspNewRow(row)) {
|
||||||
res[1] = row;
|
res[1] = row;
|
||||||
} else {
|
} else {
|
||||||
res[0].push(row);
|
res[0].push(row);
|
||||||
@ -189,6 +191,10 @@ const useRealtimeData = (formId, sheetName, direction) => {
|
|||||||
},
|
},
|
||||||
[[], null],
|
[[], null],
|
||||||
);
|
);
|
||||||
|
if (newRow === null) {
|
||||||
|
toast.error("Ошибка получения данных");
|
||||||
|
return;
|
||||||
|
}
|
||||||
setData((prevData) => {
|
setData((prevData) => {
|
||||||
const updateData = updateCells(prevData, updatedCells);
|
const updateData = updateCells(prevData, updatedCells);
|
||||||
const newData = insertCell(updateData, newRow, expense_item_id);
|
const newData = insertCell(updateData, newRow, expense_item_id);
|
||||||
|
|||||||
@ -1,22 +1,27 @@
|
|||||||
import { useEffect, useRef, useCallback, useState } from 'react';
|
import { useEffect, useRef, useCallback, useState } from "react";
|
||||||
|
|
||||||
export const useWebSocket = (url, onMessage) => {
|
export const useWebSocket = (url, onMessage) => {
|
||||||
const [isConnected, setIsConnected] = useState(false);
|
const [isConnected, setIsConnected] = useState(false);
|
||||||
|
const isConnectedRef = useRef(false);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
const wsRef = useRef(null);
|
const wsRef = useRef(null);
|
||||||
const reconnectTimeoutRef = useRef(null);
|
const reconnectTimeoutRef = useRef(null);
|
||||||
const reconnectAttempts = useRef(0);
|
const reconnectAttempts = useRef(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
isConnectedRef.current = isConnected;
|
||||||
|
}, [isConnected]);
|
||||||
|
|
||||||
const connect = useCallback(() => {
|
const connect = useCallback(() => {
|
||||||
if (wsRef.current?.readyState === WebSocket.OPEN) return;
|
if (wsRef.current?.readyState === WebSocket.OPEN) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('Connecting to WebSocket:', url);
|
console.log("Connecting to WebSocket:", url);
|
||||||
const ws = new WebSocket(url);
|
const ws = new WebSocket(url);
|
||||||
wsRef.current = ws;
|
wsRef.current = ws;
|
||||||
|
|
||||||
ws.onopen = () => {
|
ws.onopen = () => {
|
||||||
console.log('WebSocket connected successfully');
|
console.log("WebSocket connected successfully");
|
||||||
setIsConnected(true);
|
setIsConnected(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
reconnectAttempts.current = 0;
|
reconnectAttempts.current = 0;
|
||||||
@ -25,43 +30,54 @@ export const useWebSocket = (url, onMessage) => {
|
|||||||
ws.onmessage = (event) => {
|
ws.onmessage = (event) => {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(event.data);
|
const data = JSON.parse(event.data);
|
||||||
console.log('WebSocket message received:', data);
|
console.log("WebSocket message received:", data);
|
||||||
onMessage(data);
|
onMessage(data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to parse WebSocket message:', err);
|
console.error("Failed to parse WebSocket message:", err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ws.onerror = (event) => {
|
ws.onerror = (event) => {
|
||||||
console.error('WebSocket error:', event);
|
console.error("WebSocket error:", event);
|
||||||
console.error('WebSocket readyState:', ws.readyState);
|
console.error("WebSocket readyState:", ws.readyState);
|
||||||
setError('Connection error occurred');
|
setError("Connection error occurred");
|
||||||
};
|
};
|
||||||
|
|
||||||
ws.onclose = (event) => {
|
ws.onclose = (event) => {
|
||||||
console.log('WebSocket closed:', event.code, event.reason, 'Clean:', event.wasClean);
|
console.log(
|
||||||
|
"WebSocket closed:",
|
||||||
|
event.code,
|
||||||
|
event.reason,
|
||||||
|
"Clean:",
|
||||||
|
event.wasClean,
|
||||||
|
);
|
||||||
setIsConnected(false);
|
setIsConnected(false);
|
||||||
|
|
||||||
// Не переподключаемся если закрыто intentionally или с ошибкой авторизации
|
// Не переподключаемся если закрыто intentionally или с ошибкой авторизации
|
||||||
if (event.code === 1008 || event.code === 1000) {
|
if (event.code === 1008 || event.code === 1000) {
|
||||||
console.log('Connection closed intentionally or auth error, not reconnecting');
|
console.log(
|
||||||
|
"Connection closed intentionally or auth error, not reconnecting",
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reconnect with exponential backoff
|
// Reconnect with exponential backoff
|
||||||
if (reconnectAttempts.current < 5) {
|
if (reconnectAttempts.current < 5) {
|
||||||
const delay = Math.min(1000 * Math.pow(2, reconnectAttempts.current), 30000);
|
const delay = Math.min(
|
||||||
|
1000 * Math.pow(2, reconnectAttempts.current),
|
||||||
|
30000,
|
||||||
|
);
|
||||||
console.log(`Reconnecting in ${delay}ms...`);
|
console.log(`Reconnecting in ${delay}ms...`);
|
||||||
reconnectTimeoutRef.current = setTimeout(() => {
|
reconnectTimeoutRef.current = setTimeout(() => {
|
||||||
reconnectAttempts.current++;
|
reconnectAttempts.current++;
|
||||||
connect();
|
connect();
|
||||||
}, delay);
|
}, delay);
|
||||||
} else {
|
} else {
|
||||||
setError('Max reconnection attempts reached');
|
setError("Max reconnection attempts reached");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to create WebSocket:', err);
|
console.error("Failed to create WebSocket:", err);
|
||||||
setError(err.message);
|
setError(err.message);
|
||||||
}
|
}
|
||||||
}, [url, onMessage]);
|
}, [url, onMessage]);
|
||||||
@ -71,7 +87,7 @@ export const useWebSocket = (url, onMessage) => {
|
|||||||
clearTimeout(reconnectTimeoutRef.current);
|
clearTimeout(reconnectTimeoutRef.current);
|
||||||
}
|
}
|
||||||
if (wsRef.current) {
|
if (wsRef.current) {
|
||||||
wsRef.current.close(1000, 'Intentional disconnect');
|
wsRef.current.close(1000, "Intentional disconnect");
|
||||||
wsRef.current = null;
|
wsRef.current = null;
|
||||||
}
|
}
|
||||||
setIsConnected(false);
|
setIsConnected(false);
|
||||||
@ -80,24 +96,30 @@ export const useWebSocket = (url, onMessage) => {
|
|||||||
const sendMessage = useCallback((message) => {
|
const sendMessage = useCallback((message) => {
|
||||||
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
||||||
wsRef.current.send(JSON.stringify(message));
|
wsRef.current.send(JSON.stringify(message));
|
||||||
console.log('Message sent:', message);
|
console.log("Message sent:", message);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
console.warn('WebSocket is not connected, readyState:', wsRef.current?.readyState);
|
console.warn(
|
||||||
setError('Cannot send message: WebSocket not connected');
|
"WebSocket is not connected, readyState:",
|
||||||
|
wsRef.current?.readyState,
|
||||||
|
);
|
||||||
|
setError("Cannot send message: WebSocket not connected");
|
||||||
return false;
|
return false;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const login = useCallback((token) => {
|
const login = useCallback(
|
||||||
|
(token) => {
|
||||||
if (!token) {
|
if (!token) {
|
||||||
console.warn('No token provided for login');
|
console.warn("No token provided for login");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return sendMessage({
|
return sendMessage({
|
||||||
event: 'user_login',
|
event: "user_login",
|
||||||
data: { token }
|
data: { token },
|
||||||
});
|
});
|
||||||
}, [sendMessage]);
|
},
|
||||||
|
[sendMessage],
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
connect();
|
connect();
|
||||||
@ -105,10 +127,10 @@ export const useWebSocket = (url, onMessage) => {
|
|||||||
}, [connect, disconnect]);
|
}, [connect, disconnect]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isConnected,
|
isConnected: isConnectedRef,
|
||||||
error,
|
error,
|
||||||
sendMessage,
|
sendMessage,
|
||||||
login,
|
login,
|
||||||
disconnect
|
disconnect,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -90,7 +90,6 @@ export function updateCells(data, newRows) {
|
|||||||
|
|
||||||
return updatedRows;
|
return updatedRows;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function insertCell(data, newRow, expense_item_id) {
|
export function insertCell(data, newRow, expense_item_id) {
|
||||||
const updatedRows = JSON.parse(JSON.stringify(data));
|
const updatedRows = JSON.parse(JSON.stringify(data));
|
||||||
const [row_type, depth, sort_order, dataRow] = newRow;
|
const [row_type, depth, sort_order, dataRow] = newRow;
|
||||||
@ -102,6 +101,12 @@ export function insertCell(data, newRow, expense_item_id) {
|
|||||||
data: dataRow,
|
data: dataRow,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Если expense_item_id === null, добавляем в конец
|
||||||
|
if (expense_item_id === null) {
|
||||||
|
updatedRows.push(newRowObject);
|
||||||
|
return updatedRows;
|
||||||
|
}
|
||||||
|
|
||||||
function findAndUpdate(rows) {
|
function findAndUpdate(rows) {
|
||||||
for (let i = 0; i < rows.length; i++) {
|
for (let i = 0; i < rows.length; i++) {
|
||||||
const row = rows[i];
|
const row = rows[i];
|
||||||
|
|||||||
15
web/src/components/RealtimeTable/utils/rowUtils.js
Normal file
15
web/src/components/RealtimeTable/utils/rowUtils.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
export function getRowId(row) {
|
||||||
|
return row.original.data.id || row.original.data.line_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isVspRow(row) {
|
||||||
|
if (row?.original?.data?.id) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
export function isVspNewRow(rowData) {
|
||||||
|
if (!rowData) return false;
|
||||||
|
const newRow = rowData[3];
|
||||||
|
return !!newRow.id;
|
||||||
|
}
|
||||||
@ -6,7 +6,7 @@ const ModalContainer = styled.div`
|
|||||||
/* top: 3rem;
|
/* top: 3rem;
|
||||||
left: 0; */
|
left: 0; */
|
||||||
width: 50rem;
|
width: 50rem;
|
||||||
height: 40rem;
|
height: ${({ height }) => height || '40rem'};
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
@ -288,6 +288,28 @@ const EditContainer = styled.div`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const ModalBackdrop = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0, 0, 0, 0.05);
|
||||||
|
display: ${(props) => (props.isOpen ? 'flex' : 'none')};
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 3;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ModalTitle = styled.h2`
|
||||||
|
color: rgba(31, 31, 31, 1);
|
||||||
|
font-size: 1.125rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0;
|
||||||
|
`;
|
||||||
|
|
||||||
export {
|
export {
|
||||||
ModalContainer,
|
ModalContainer,
|
||||||
HeaderContainer,
|
HeaderContainer,
|
||||||
@ -304,4 +326,6 @@ export {
|
|||||||
StageName,
|
StageName,
|
||||||
StageListContainer,
|
StageListContainer,
|
||||||
EditContainer,
|
EditContainer,
|
||||||
|
ModalBackdrop,
|
||||||
|
ModalTitle
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user