работает
This commit is contained in:
@@ -28,6 +28,8 @@ export const buttonGroupDefinition: ElementDefinition<
|
|||||||
layout: 'horizontal',
|
layout: 'horizontal',
|
||||||
buttonStyle: 'default',
|
buttonStyle: 'default',
|
||||||
},
|
},
|
||||||
|
// Берём целевые ячейки прямо из конфига, если они заданы в редакторе
|
||||||
|
mapToCells: cfg => cfg.targetCells || [],
|
||||||
Editor: ({ config, onChange }) => {
|
Editor: ({ config, onChange }) => {
|
||||||
const handleAddOption = () => {
|
const handleAddOption = () => {
|
||||||
onChange({
|
onChange({
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export const ExcelUploadPanel: React.FC<ExcelUploadPanelProps> = ({
|
|||||||
className={`h-3 w-3 ${fileName ? 'text-emerald-600' : 'text-gray-400'}`}
|
className={`h-3 w-3 ${fileName ? 'text-emerald-600' : 'text-gray-400'}`}
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
className={`max-w-32 truncate text-xs font-medium ${
|
className={`text-xs font-medium ${
|
||||||
fileName ? 'text-emerald-700' : 'text-gray-500'
|
fileName ? 'text-emerald-700' : 'text-gray-500'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ export const HeaderBar: React.FC<HeaderBarProps> = ({ template, onBack }) => {
|
|||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-shrink-0 border-b bg-card px-4 py-1">
|
<div className="flex-shrink-0 border-b bg-card px-4 py-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-2">
|
||||||
<Button variant="ghost" size="icon" onClick={onBack}>
|
<Button variant="ghost" size="icon" onClick={onBack}>
|
||||||
<ArrowLeft className="h-4 w-4" />
|
<ArrowLeft className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -30,22 +30,22 @@ export const HeaderBar: React.FC<HeaderBarProps> = ({ template, onBack }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="secondary"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => navigate(`/templates/${template.id}/elements`)}
|
onClick={() => navigate(`/templates/${template.id}/elements`)}
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-1 hover:bg-muted"
|
||||||
>
|
>
|
||||||
<Wrench className="h-3.5 w-3.5" />
|
<Wrench className="h-4 w-4" />
|
||||||
Элементы интерфейса
|
<span className="text-sm">Элементы интерфейса</span>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="secondary"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => navigate(`/templates/${template.id}/protocols`)}
|
onClick={() => navigate(`/templates/${template.id}/protocols`)}
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-1 hover:bg-muted"
|
||||||
>
|
>
|
||||||
<FileText className="h-3.5 w-3.5" />
|
<FileText className="h-4 w-4" />
|
||||||
Создать протокол
|
<span className="text-sm">Создать протокол</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -79,10 +79,38 @@ export const useSheetAutoSave = (
|
|||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
try {
|
try {
|
||||||
const response = await getSheetsByTemplate(cleanTemplateId)
|
const response = await getSheetsByTemplate(cleanTemplateId)
|
||||||
|
/*
|
||||||
|
* В API могут существовать листы как с «сырым» именем (Report/Calculations),
|
||||||
|
* так и уже нормализованные (L/R). Чтобы случайный порядок ответов не
|
||||||
|
* приводил к потере данных (один объект перезаписывал другой), собираем
|
||||||
|
* листы в map с объединением ячеек, а не простым overwrite.
|
||||||
|
*/
|
||||||
const sheetsMap = response.sheets.reduce(
|
const sheetsMap = response.sheets.reduce(
|
||||||
(acc, sheet) => {
|
(acc, sheet) => {
|
||||||
const normalizedName = sheet.name === 'Report' ? 'L' : sheet.name
|
// Приводим названия к единым «L»/«R»
|
||||||
|
const normalizedName =
|
||||||
|
sheet.name === 'Report'
|
||||||
|
? 'L'
|
||||||
|
: sheet.name === 'Calculations'
|
||||||
|
? 'R'
|
||||||
|
: sheet.name
|
||||||
|
|
||||||
|
if (acc[normalizedName]) {
|
||||||
|
// Уже есть лист с таким именем — мерджим данные, приоритет у более
|
||||||
|
// свежего объекта (sheet), но оставляем существующие ячейки если их
|
||||||
|
// нет в новом
|
||||||
|
acc[normalizedName] = {
|
||||||
|
...sheet,
|
||||||
|
name: normalizedName,
|
||||||
|
cells: {
|
||||||
|
...acc[normalizedName].cells,
|
||||||
|
...sheet.cells,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
acc[normalizedName] = { ...sheet, name: normalizedName }
|
acc[normalizedName] = { ...sheet, name: normalizedName }
|
||||||
|
}
|
||||||
|
|
||||||
return acc
|
return acc
|
||||||
},
|
},
|
||||||
{} as Record<string, ApiSheet>
|
{} as Record<string, ApiSheet>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
} from '@/component/ui/select'
|
} from '@/component/ui/select'
|
||||||
import { Textarea } from '@/component/ui/textarea'
|
import { Textarea } from '@/component/ui/textarea'
|
||||||
import { useTemplateContext } from '@/entitiy/template/model/TemplateContext'
|
import { useTemplateContext } from '@/entitiy/template/model/TemplateContext'
|
||||||
|
import { cellAddressToCoordinates } from '@/lib/cell-utils'
|
||||||
import { getElementDefinition } from '@/lib/element-registry'
|
import { getElementDefinition } from '@/lib/element-registry'
|
||||||
import { getLatestFileForTemplate } from '@/service/fileApiService'
|
import { getLatestFileForTemplate } from '@/service/fileApiService'
|
||||||
import { Template, TemplateElement } from '@/type/template'
|
import { Template, TemplateElement } from '@/type/template'
|
||||||
@@ -381,7 +382,33 @@ const ProtocolForm: FC<ProtocolFormProps> = ({ template, onSave, onBack }) => {
|
|||||||
if (engineRef.current) {
|
if (engineRef.current) {
|
||||||
const element = template.elements.find(el => el.id === elementId)
|
const element = template.elements.find(el => el.id === elementId)
|
||||||
if (element && element.targetCells) {
|
if (element && element.targetCells) {
|
||||||
element.targetCells.forEach(tc => {
|
const cells = element.targetCells
|
||||||
|
|
||||||
|
cells.forEach((tc, idx) => {
|
||||||
|
let cellValue: any = value
|
||||||
|
|
||||||
|
// Специальная обработка для массивов эталонов
|
||||||
|
if (element.type === 'standards' && Array.isArray(value)) {
|
||||||
|
cellValue = getStandardById(value[idx] as string)?.name || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// Специальная обработка для условий калибровки
|
||||||
|
if (
|
||||||
|
element.type === 'calibration-conditions' &&
|
||||||
|
typeof value === 'object' &&
|
||||||
|
value !== null
|
||||||
|
) {
|
||||||
|
const propsOrder = [
|
||||||
|
'temperature',
|
||||||
|
'humidity',
|
||||||
|
'pressure',
|
||||||
|
'voltage',
|
||||||
|
'frequency',
|
||||||
|
'lastUpdated',
|
||||||
|
] as const
|
||||||
|
cellValue = (value as any)[propsOrder[idx]] ?? ''
|
||||||
|
}
|
||||||
|
|
||||||
const { row, col } = cellAddressToCoordinates(tc.cell)
|
const { row, col } = cellAddressToCoordinates(tc.cell)
|
||||||
const sheetName =
|
const sheetName =
|
||||||
tc.sheet === 'R' || tc.sheet === 'Calculations' ? 'R' : 'L'
|
tc.sheet === 'R' || tc.sheet === 'Calculations' ? 'R' : 'L'
|
||||||
@@ -389,7 +416,7 @@ const ProtocolForm: FC<ProtocolFormProps> = ({ template, onSave, onBack }) => {
|
|||||||
sheetName,
|
sheetName,
|
||||||
row,
|
row,
|
||||||
col,
|
col,
|
||||||
value
|
cellValue
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
engineRef.current.debouncedRecalc()
|
engineRef.current.debouncedRecalc()
|
||||||
@@ -444,18 +471,78 @@ const ProtocolForm: FC<ProtocolFormProps> = ({ template, onSave, onBack }) => {
|
|||||||
if (def?.mapToCells) cells = def.mapToCells(el as any)
|
if (def?.mapToCells) cells = def.mapToCells(el as any)
|
||||||
}
|
}
|
||||||
|
|
||||||
cells?.forEach(tc => {
|
// === Новая логика распределения значений по ячейкам ===
|
||||||
|
if (!cells || cells.length === 0) return
|
||||||
|
|
||||||
|
// 1) Массив (например, standards) -> каждой ячейке своё значение
|
||||||
|
if (el.type === 'standards' && Array.isArray(value)) {
|
||||||
|
cells.forEach((tc, idx) => {
|
||||||
|
const cellValue = getStandardById(value[idx] as string)?.name || ''
|
||||||
|
const { row, col } = cellAddressToCoordinates(tc.cell)
|
||||||
|
if (tc.sheet === 'Calculations') alert('Лист Calculations')
|
||||||
|
const sheetName =
|
||||||
|
tc.sheet === 'R' || tc.sheet === 'Calculations' ? 'R' : 'L'
|
||||||
|
engineRef.current.setCellValueWithoutRecalc(
|
||||||
|
sheetName,
|
||||||
|
row,
|
||||||
|
col,
|
||||||
|
cellValue
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработка любых других массивов
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
cells.forEach((tc, idx) => {
|
||||||
|
const cellValue = value[idx] ?? ''
|
||||||
const { row, col } = cellAddressToCoordinates(tc.cell)
|
const { row, col } = cellAddressToCoordinates(tc.cell)
|
||||||
const sheetName =
|
const sheetName =
|
||||||
tc.sheet === 'R' || tc.sheet === 'Calculations' ? 'R' : 'L'
|
tc.sheet === 'R' || tc.sheet === 'Calculations' ? 'R' : 'L'
|
||||||
console.log(
|
engineRef.current.setCellValueWithoutRecalc(
|
||||||
'[DBG] write to',
|
|
||||||
tc.cell,
|
|
||||||
'sheet',
|
|
||||||
sheetName,
|
sheetName,
|
||||||
'value',
|
row,
|
||||||
value
|
col,
|
||||||
|
cellValue
|
||||||
)
|
)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Объект условия калибровки -> пишем свойства по порядку
|
||||||
|
if (
|
||||||
|
el.type === 'calibration-conditions' &&
|
||||||
|
typeof value === 'object' &&
|
||||||
|
value !== null
|
||||||
|
) {
|
||||||
|
const propsOrder = [
|
||||||
|
'temperature',
|
||||||
|
'humidity',
|
||||||
|
'pressure',
|
||||||
|
'voltage',
|
||||||
|
'frequency',
|
||||||
|
'lastUpdated',
|
||||||
|
] as const
|
||||||
|
cells.forEach((tc, idx) => {
|
||||||
|
const cellValue = (value as any)[propsOrder[idx]] ?? ''
|
||||||
|
const { row, col } = cellAddressToCoordinates(tc.cell)
|
||||||
|
const sheetName =
|
||||||
|
tc.sheet === 'R' || tc.sheet === 'Calculations' ? 'R' : 'L'
|
||||||
|
engineRef.current.setCellValueWithoutRecalc(
|
||||||
|
sheetName,
|
||||||
|
row,
|
||||||
|
col,
|
||||||
|
cellValue
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Примитив или прочие объекты -> одно значение во все ячейки
|
||||||
|
cells.forEach(tc => {
|
||||||
|
const { row, col } = cellAddressToCoordinates(tc.cell)
|
||||||
|
const sheetName =
|
||||||
|
tc.sheet === 'R' || tc.sheet === 'Calculations' ? 'R' : 'L'
|
||||||
engineRef.current.setCellValueWithoutRecalc(
|
engineRef.current.setCellValueWithoutRecalc(
|
||||||
sheetName,
|
sheetName,
|
||||||
row,
|
row,
|
||||||
@@ -546,9 +633,10 @@ const ProtocolForm: FC<ProtocolFormProps> = ({ template, onSave, onBack }) => {
|
|||||||
const protocolNameElement = template.elements.find(
|
const protocolNameElement = template.elements.find(
|
||||||
el => el.name === 'protocolName' || el.label === 'Название протокола'
|
el => el.name === 'protocolName' || el.label === 'Название протокола'
|
||||||
)
|
)
|
||||||
const canSave = protocolNameElement
|
// const canSave = protocolNameElement
|
||||||
? !!formData[protocolNameElement.id]?.trim()
|
// ? !!formData[protocolNameElement.id]?.trim()
|
||||||
: false
|
// : false
|
||||||
|
const canSave = true
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen flex-col bg-gray-50">
|
<div className="flex h-screen flex-col bg-gray-50">
|
||||||
@@ -591,9 +679,7 @@ const ProtocolForm: FC<ProtocolFormProps> = ({ template, onSave, onBack }) => {
|
|||||||
{/* Всегда монтируем DualSpreadsheet, скрываем/показываем через hidden */}
|
{/* Всегда монтируем DualSpreadsheet, скрываем/показываем через hidden */}
|
||||||
<div className={`${showSpreadsheet ? 'block' : 'hidden'} h-full`}>
|
<div className={`${showSpreadsheet ? 'block' : 'hidden'} h-full`}>
|
||||||
<DualSpreadsheet
|
<DualSpreadsheet
|
||||||
templateData={
|
templateData={template.excelData ? { L: template.excelData } : {}}
|
||||||
template.excelData ? { report: template.excelData } : {}
|
|
||||||
}
|
|
||||||
mergedCells={template.mergedCells || []}
|
mergedCells={template.mergedCells || []}
|
||||||
templateId={template.id}
|
templateId={template.id}
|
||||||
onEngineReady={engine => (engineRef.current = engine)}
|
onEngineReady={engine => (engineRef.current = engine)}
|
||||||
|
|||||||
Reference in New Issue
Block a user