vsp-choose-form2-2: выбор ВСП на фронте
This commit is contained in:
parent
1eb5959a50
commit
e0c8c57fc2
@ -134,24 +134,34 @@ const CellComponent = ({
|
|||||||
isUpdating,
|
isUpdating,
|
||||||
onCellNumberClick,
|
onCellNumberClick,
|
||||||
isInvalid,
|
isInvalid,
|
||||||
|
vspOptions,
|
||||||
}) => {
|
}) => {
|
||||||
const { lockedCells } = useRealtime();
|
const { lockedCells } = useRealtime();
|
||||||
const cellKey = `${row.id}_${column.id}`;
|
const cellKey = `${row.id}_${column.id}`;
|
||||||
const isLocked = lockedCells.includes(cellKey);
|
const isLocked = lockedCells.includes(cellKey);
|
||||||
|
|
||||||
|
const isVspDropdown = column?.columnDef?.editType === 'vsp_dropdown';
|
||||||
|
|
||||||
// Мемоизация значения
|
// Мемоизация значения
|
||||||
const { rawValue, displayValue, isNumeric } = useMemo(() => {
|
const { rawValue, displayValue, isNumeric } = useMemo(() => {
|
||||||
const value = cell.getValue();
|
const value = cell.getValue();
|
||||||
const isNum = !isNaN(Number(value)) && value !== null && value !== undefined && value !== '';
|
const isNum = !isNaN(Number(value)) && value !== null && value !== undefined && value !== '';
|
||||||
let display = String(value || '');
|
let display = String(value || '');
|
||||||
|
|
||||||
|
if (isVspDropdown && value != null) {
|
||||||
|
const vsp = vspOptions?.find(v => v.id === Number(value));
|
||||||
|
if (vsp) {
|
||||||
|
return { rawValue: value, displayValue: vsp.registration_number, isNumeric: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isNum) {
|
if (isNum) {
|
||||||
const asInteger = INTEGER_COLUMN_IDS.has(column.id);
|
const asInteger = INTEGER_COLUMN_IDS.has(column.id);
|
||||||
display = formatNumber(value, asInteger);
|
display = formatNumber(value, asInteger);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { rawValue: value, displayValue: display, isNumeric: isNum };
|
return { rawValue: value, displayValue: display, isNumeric: isNum };
|
||||||
}, [cell, column.id]);
|
}, [cell, column.id, isVspDropdown, vspOptions]);
|
||||||
|
|
||||||
const highlightedContent = useMemo(() => {
|
const highlightedContent = useMemo(() => {
|
||||||
const searchQueries = [globalFilter, columnFilter];
|
const searchQueries = [globalFilter, columnFilter];
|
||||||
@ -207,7 +217,8 @@ const Cell = React.memo(CellComponent, (prevProps, nextProps) => {
|
|||||||
prevProps.isInvalid === nextProps.isInvalid &&
|
prevProps.isInvalid === nextProps.isInvalid &&
|
||||||
prevProps.isEditable === nextProps.isEditable &&
|
prevProps.isEditable === nextProps.isEditable &&
|
||||||
prevProps.isUpdating === nextProps.isUpdating &&
|
prevProps.isUpdating === nextProps.isUpdating &&
|
||||||
prevProps.onCellNumberClick === nextProps.onCellNumberClick
|
prevProps.onCellNumberClick === nextProps.onCellNumberClick &&
|
||||||
|
prevProps.vspOptions === nextProps.vspOptions
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,113 @@
|
|||||||
|
import React, { memo, useEffect, useRef, useLayoutEffect, useState, useCallback, useMemo } from 'react';
|
||||||
|
import { FormControl, Select, MenuItem } from '@mui/material';
|
||||||
|
import { useRealtime } from '../../contexts/RealtimeContext';
|
||||||
|
|
||||||
|
const EDITOR_STYLES = {
|
||||||
|
position: 'absolute',
|
||||||
|
zIndex: 12,
|
||||||
|
top: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const VspDropdownEditCell = memo(({ refCell, onChange, disabled, value, table }) => {
|
||||||
|
const { endEditing: contextEndEditing, vspOptions } = useRealtime();
|
||||||
|
|
||||||
|
const [selectedValue, setSelectedValue] = useState(() => value ?? '');
|
||||||
|
const [position, setPosition] = useState();
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const refFinished = useRef(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setOpen(true);
|
||||||
|
}, 80);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (!refCell?.current) return;
|
||||||
|
|
||||||
|
const td = refCell.current.offsetParent;
|
||||||
|
const tr = td?.offsetParent;
|
||||||
|
|
||||||
|
if (td && tr) {
|
||||||
|
setPosition({
|
||||||
|
left: td.offsetLeft,
|
||||||
|
width: td.offsetWidth,
|
||||||
|
translateY: tr.style.transform || '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [refCell]);
|
||||||
|
|
||||||
|
const finishEditing = useCallback(() => {
|
||||||
|
if (refFinished.current) return;
|
||||||
|
refFinished.current = true;
|
||||||
|
table.setEditingCell(null);
|
||||||
|
const editingCell = table.getState().editingCell;
|
||||||
|
if (editingCell) {
|
||||||
|
contextEndEditing?.(editingCell.row, editingCell.column);
|
||||||
|
}
|
||||||
|
}, [table, contextEndEditing]);
|
||||||
|
|
||||||
|
const handleSelectChange = useCallback((e) => {
|
||||||
|
const val = e.target.value;
|
||||||
|
if (val == null || val === '') return;
|
||||||
|
refFinished.current = true;
|
||||||
|
setSelectedValue(val);
|
||||||
|
onChange?.(val);
|
||||||
|
const editingCell = table.getState().editingCell;
|
||||||
|
if (editingCell) {
|
||||||
|
contextEndEditing?.(editingCell.row, editingCell.column);
|
||||||
|
}
|
||||||
|
}, [onChange, table, contextEndEditing]);
|
||||||
|
|
||||||
|
const handleSelectClose = useCallback((_event, reason) => {
|
||||||
|
if (reason === 'selectOption') return;
|
||||||
|
finishEditing();
|
||||||
|
}, [finishEditing]);
|
||||||
|
|
||||||
|
const editorStyles = useMemo(() => ({
|
||||||
|
...EDITOR_STYLES,
|
||||||
|
left: position?.left || 0,
|
||||||
|
width: position?.width || 0,
|
||||||
|
transform: position?.translateY || 0,
|
||||||
|
}), [position]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{position &&
|
||||||
|
<div style={editorStyles}>
|
||||||
|
<FormControl fullWidth size="small">
|
||||||
|
<Select
|
||||||
|
value={selectedValue}
|
||||||
|
onChange={handleSelectChange}
|
||||||
|
open={open}
|
||||||
|
onClose={handleSelectClose}
|
||||||
|
displayEmpty
|
||||||
|
disabled={disabled}
|
||||||
|
MenuProps={{
|
||||||
|
anchorOrigin: { vertical: 'bottom', horizontal: 'left' },
|
||||||
|
transformOrigin: { vertical: 'top', horizontal: 'left' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled>
|
||||||
|
Выберите ВСП
|
||||||
|
</MenuItem>
|
||||||
|
{vspOptions.map((option) => (
|
||||||
|
<MenuItem key={option.id} value={option.id}>
|
||||||
|
{option.registration_number}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
VspDropdownEditCell.displayName = 'VspDropdownEditCell';
|
||||||
|
|
||||||
|
export default VspDropdownEditCell;
|
||||||
@ -32,6 +32,7 @@ import { getRowId } from './utils/rowUtils';
|
|||||||
import { FORM_TYPE_OPTIONS } from '../../constants/constants';
|
import { FORM_TYPE_OPTIONS } from '../../constants/constants';
|
||||||
import { debounce } from '@mui/material';
|
import { debounce } from '@mui/material';
|
||||||
import { CreateProgramModal } from './Modals/CreateProgramProjectModal';
|
import { CreateProgramModal } from './Modals/CreateProgramProjectModal';
|
||||||
|
import { DictVspApi } from '../../api/dict-vsp';
|
||||||
|
|
||||||
const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
||||||
const { data, setData, isLoading: isTableLoading, editingCells: dataEditingCells } = useRealtimeData(formId, sheetName, direction, formType, year);
|
const { data, setData, isLoading: isTableLoading, editingCells: dataEditingCells } = useRealtimeData(formId, sheetName, direction, formType, year);
|
||||||
@ -87,6 +88,7 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
|||||||
startEditing: contextStartEditing,
|
startEditing: contextStartEditing,
|
||||||
addProgram: contextAddProgram,
|
addProgram: contextAddProgram,
|
||||||
addProject: contextAddProject,
|
addProject: contextAddProject,
|
||||||
|
setVspOptions,
|
||||||
} = useRealtime();
|
} = useRealtime();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -136,6 +138,26 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
|||||||
loadConfig();
|
loadConfig();
|
||||||
}, [formType, sheetName]);
|
}, [formType, sheetName]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!formId || !columnsConfig?.columns) return;
|
||||||
|
|
||||||
|
const hasVspDropdown = (cols) => {
|
||||||
|
for (const col of cols) {
|
||||||
|
if (col.editType === 'vsp_dropdown') return true;
|
||||||
|
if (col.columns && hasVspDropdown(col.columns)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!hasVspDropdown(columnsConfig.columns)) return;
|
||||||
|
|
||||||
|
DictVspApi.getDropdownVsp({ form_id: formId }).then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
setVspOptions(data.result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [formId, columnsConfig, setVspOptions]);
|
||||||
|
|
||||||
const handleUpdateCell = useCallback(async (row, column, value) => {
|
const handleUpdateCell = useCallback(async (row, column, value) => {
|
||||||
return await contextUpdateCell(row, column, value);
|
return await contextUpdateCell(row, column, value);
|
||||||
}, [contextUpdateCell]);
|
}, [contextUpdateCell]);
|
||||||
|
|||||||
@ -29,6 +29,10 @@ export const config = {
|
|||||||
color_type: greenColumn,
|
color_type: greenColumn,
|
||||||
accessorKey: "data.header.vsp_id",
|
accessorKey: "data.header.vsp_id",
|
||||||
},
|
},
|
||||||
|
"data.header.vsp_address": {
|
||||||
|
color_type: greenColumn,
|
||||||
|
accessorKey: "data.header.vsp_address",
|
||||||
|
},
|
||||||
"data.plan.q1": {
|
"data.plan.q1": {
|
||||||
color_type: greenColumn,
|
color_type: greenColumn,
|
||||||
accessorKey: "data.plan.q1",
|
accessorKey: "data.plan.q1",
|
||||||
@ -725,6 +729,15 @@ export const config = {
|
|||||||
columnLetter: "E",
|
columnLetter: "E",
|
||||||
size: 150,
|
size: 150,
|
||||||
filterFn: "contains",
|
filterFn: "contains",
|
||||||
|
editType: "vsp_dropdown",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "Адрес ВСП",
|
||||||
|
accessorKey: "data.header.vsp_address",
|
||||||
|
columnLetter: "E1",
|
||||||
|
size: 220,
|
||||||
|
filterFn: "contains",
|
||||||
|
editType: "vsp_dropdown",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
muiTableHeadCellProps: {
|
muiTableHeadCellProps: {
|
||||||
|
|||||||
@ -25,6 +25,14 @@ export const config = {
|
|||||||
color_type: orangeColumn,
|
color_type: orangeColumn,
|
||||||
accessorKey: "data.header.name",
|
accessorKey: "data.header.name",
|
||||||
},
|
},
|
||||||
|
"data.header.vsp_id": {
|
||||||
|
color_type: orangeColumn,
|
||||||
|
accessorKey: "data.header.vsp_id",
|
||||||
|
},
|
||||||
|
"data.header.vsp_address": {
|
||||||
|
color_type: orangeColumn,
|
||||||
|
accessorKey: "data.header.vsp_address",
|
||||||
|
},
|
||||||
"data.header.internal_order": {
|
"data.header.internal_order": {
|
||||||
color_type: orangeColumn,
|
color_type: orangeColumn,
|
||||||
accessorKey: "data.header.internal_order",
|
accessorKey: "data.header.internal_order",
|
||||||
@ -830,6 +838,33 @@ export const config = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
header: "ВСП РФ",
|
||||||
|
accessorKey: "field_vsp_rf",
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
header: "",
|
||||||
|
accessorKey: "data.header.vsp_id",
|
||||||
|
columnLetter: "G",
|
||||||
|
size: 150,
|
||||||
|
filterFn: "contains",
|
||||||
|
editType: "vsp_dropdown",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "Адрес ВСП",
|
||||||
|
accessorKey: "field_adres_vsp",
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
header: "",
|
||||||
|
accessorKey: "data.header.vsp_address",
|
||||||
|
columnLetter: "G1",
|
||||||
|
size: 220,
|
||||||
|
filterFn: "contains",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
header: "Внутренний заказ",
|
header: "Внутренний заказ",
|
||||||
accessorKey: "field_vnutrenniy_zakaz",
|
accessorKey: "field_vnutrenniy_zakaz",
|
||||||
@ -837,7 +872,7 @@ export const config = {
|
|||||||
{
|
{
|
||||||
header: "",
|
header: "",
|
||||||
accessorKey: "data.header.internal_order",
|
accessorKey: "data.header.internal_order",
|
||||||
columnLetter: "G",
|
columnLetter: "H",
|
||||||
size: 150,
|
size: 150,
|
||||||
filterFn: "contains",
|
filterFn: "contains",
|
||||||
},
|
},
|
||||||
|
|||||||
@ -25,6 +25,14 @@ export const config = {
|
|||||||
color_type: blueColumn,
|
color_type: blueColumn,
|
||||||
accessorKey: "data.header.name",
|
accessorKey: "data.header.name",
|
||||||
},
|
},
|
||||||
|
"data.header.vsp_id": {
|
||||||
|
color_type: blueColumn,
|
||||||
|
accessorKey: "data.header.vsp_id",
|
||||||
|
},
|
||||||
|
"data.header.vsp_address": {
|
||||||
|
color_type: blueColumn,
|
||||||
|
accessorKey: "data.header.vsp_address",
|
||||||
|
},
|
||||||
"data.header.internal_order": {
|
"data.header.internal_order": {
|
||||||
color_type: blueColumn,
|
color_type: blueColumn,
|
||||||
accessorKey: "data.header.internal_order",
|
accessorKey: "data.header.internal_order",
|
||||||
@ -830,6 +838,33 @@ export const config = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
header: "ВСП РФ",
|
||||||
|
accessorKey: "field_vsp_rf",
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
header: "",
|
||||||
|
accessorKey: "data.header.vsp_id",
|
||||||
|
columnLetter: "G",
|
||||||
|
size: 150,
|
||||||
|
filterFn: "contains",
|
||||||
|
editType: "vsp_dropdown",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "Адрес ВСП",
|
||||||
|
accessorKey: "field_adres_vsp",
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
header: "",
|
||||||
|
accessorKey: "data.header.vsp_address",
|
||||||
|
columnLetter: "G1",
|
||||||
|
size: 220,
|
||||||
|
filterFn: "contains",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
header: "Внутренний заказ",
|
header: "Внутренний заказ",
|
||||||
accessorKey: "field_vnutrenniy_zakaz",
|
accessorKey: "field_vnutrenniy_zakaz",
|
||||||
@ -837,7 +872,7 @@ export const config = {
|
|||||||
{
|
{
|
||||||
header: "",
|
header: "",
|
||||||
accessorKey: "data.header.internal_order",
|
accessorKey: "data.header.internal_order",
|
||||||
columnLetter: "G",
|
columnLetter: "H",
|
||||||
size: 150,
|
size: 150,
|
||||||
filterFn: "contains",
|
filterFn: "contains",
|
||||||
},
|
},
|
||||||
|
|||||||
@ -43,6 +43,7 @@ export const RealtimeProvider = ({
|
|||||||
|
|
||||||
//для ячеек которые редактируются другими пользователями
|
//для ячеек которые редактируются другими пользователями
|
||||||
const [lockedCells, setLockedCells] = useState([]);
|
const [lockedCells, setLockedCells] = useState([]);
|
||||||
|
const [vspOptions, setVspOptions] = useState([]);
|
||||||
const handleMessage = useCallback((data) => {
|
const handleMessage = useCallback((data) => {
|
||||||
// Обработка ошибок
|
// Обработка ошибок
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
@ -445,6 +446,8 @@ export const RealtimeProvider = ({
|
|||||||
retryLogin,
|
retryLogin,
|
||||||
lockedCells,
|
lockedCells,
|
||||||
setLockedCells,
|
setLockedCells,
|
||||||
|
vspOptions,
|
||||||
|
setVspOptions,
|
||||||
releaseAllLocks: () => releaseAllLocks(userId),
|
releaseAllLocks: () => releaseAllLocks(userId),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import React, { useEffect, useRef, useState, useMemo, useCallback } from 'react'
|
|||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { useParams } from 'react-router';
|
import { useParams } from 'react-router';
|
||||||
import { useRealtime } from './contexts/RealtimeContext';
|
import { useRealtime } from './contexts/RealtimeContext';
|
||||||
|
import VspDropdownEditCell from './Cell/EditCell/VspDropdownEditCell';
|
||||||
|
|
||||||
const EditCellPortal = React.memo(({
|
const EditCellPortal = React.memo(({
|
||||||
cell,
|
cell,
|
||||||
@ -60,13 +61,23 @@ const EditCellPortal = React.memo(({
|
|||||||
cellId: `${cell.row.id}_${cell.column.id}`,
|
cellId: `${cell.row.id}_${cell.column.id}`,
|
||||||
}), [cell, isEditable, handleChange, tableKey, table]);
|
}), [cell, isEditable, handleChange, tableKey, table]);
|
||||||
|
|
||||||
|
const editType = cell.column.columnDef?.editType;
|
||||||
|
|
||||||
const portalContent = useMemo(() => {
|
const portalContent = useMemo(() => {
|
||||||
if (!refTbody || isSaving) return null;
|
if (!refTbody || isSaving) return null;
|
||||||
|
|
||||||
|
if (editType === 'vsp_dropdown') {
|
||||||
|
return createPortal(
|
||||||
|
<VspDropdownEditCell {...editCellProps} />,
|
||||||
|
refTbody
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return createPortal(
|
return createPortal(
|
||||||
<EditCell {...editCellProps} />,
|
<EditCell {...editCellProps} />,
|
||||||
refTbody
|
refTbody
|
||||||
);
|
);
|
||||||
}, [refTbody, isSaving, editCellProps, EditCell]);
|
}, [refTbody, isSaving, editCellProps, EditCell, editType]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -152,18 +163,19 @@ export const getTableColumns = ({
|
|||||||
return props;
|
return props;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Оптимизированная функция processColumns
|
|
||||||
const processColumns = (columns) => {
|
const processColumns = (columns) => {
|
||||||
columns.forEach((col) => {
|
columns.forEach((col) => {
|
||||||
col.Cell = ({ cell, table, column, row }) => {
|
col.Cell = ({ cell, table, column, row }) => {
|
||||||
|
const { vspOptions } = useRealtime();
|
||||||
const props = getCachedCellProps(row, column, table);
|
const props = getCachedCellProps(row, column, table);
|
||||||
|
|
||||||
const cellProps = useMemo(() => ({
|
const cellProps = useMemo(() => ({
|
||||||
row,
|
row,
|
||||||
column,
|
column,
|
||||||
cell,
|
cell,
|
||||||
|
vspOptions,
|
||||||
...props,
|
...props,
|
||||||
}), [row.id, column.id, cell.getValue(), props._hash]);
|
}), [row.id, column.id, cell.getValue(), props._hash, vspOptions]);
|
||||||
|
|
||||||
return <Cell {...cellProps} />;
|
return <Cell {...cellProps} />;
|
||||||
};
|
};
|
||||||
@ -184,6 +196,10 @@ export const getTableColumns = ({
|
|||||||
return editComponent;
|
return editComponent;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (col.editType === 'vsp_dropdown') {
|
||||||
|
col.enableEditing = (row) => row?.original?.row_type === 'INPUT';
|
||||||
|
}
|
||||||
|
|
||||||
if (col.columns?.length) {
|
if (col.columns?.length) {
|
||||||
processColumns(col.columns);
|
processColumns(col.columns);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user