переименования папок

This commit is contained in:
2025-07-21 15:02:10 +03:00
parent d0c79bd334
commit 84f0c9c6aa
72 changed files with 245 additions and 249 deletions

View File

@@ -43,6 +43,7 @@
"tailwind-merge": "^3.3.1" "tailwind-merge": "^3.3.1"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^24.0.15",
"@types/react": "^18.0.37", "@types/react": "^18.0.37",
"@types/react-beautiful-dnd": "^13.1.8", "@types/react-beautiful-dnd": "^13.1.8",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",

View File

@@ -2,10 +2,10 @@ import { FC } from 'react'
import { Navigate, Route, Routes } from 'react-router-dom' import { Navigate, Route, Routes } from 'react-router-dom'
import { TemplateProvider } from '../contexts/TemplateContext' import { TemplateProvider } from '../context/TemplateContext'
import { ElementsCreation } from '../pages/ElementsCreation' import { ElementsCreation } from '../page/ElementsCreation'
import { ProtocolCreation } from '../pages/ProtocolCreation' import { ProtocolCreation } from '../page/ProtocolCreation'
import { TemplateSetup } from '../pages/TemplateSetup' import { TemplateSetup } from '../page/TemplateSetup'
import { Layout } from './Layout' import { Layout } from './Layout'
const App: FC = () => { const App: FC = () => {

View File

@@ -1,7 +1,7 @@
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input' import { Input } from '@/components/ui/input'
import { ElementDefinition } from '@/lib/element-registry' import { ElementDefinition } from '@/lib/element-registry'
import { ElementOption } from '@/types/template' import { ElementOption } from '@/type/template'
import { Plus, Square, Trash2 } from 'lucide-react' import { Plus, Square, Trash2 } from 'lucide-react'
interface ButtonGroupConfig { interface ButtonGroupConfig {
@@ -39,12 +39,12 @@ export const buttonGroupDefinition: ElementDefinition<
const handleUpdateOption = ( const handleUpdateOption = (
index: number, index: number,
field: 'value' | 'label', field: 'value' | 'label',
value: string, value: string
) => { ) => {
onChange({ onChange({
...config, ...config,
options: config.options.map((option, i) => options: config.options.map((option, i) =>
i === index ? { ...option, [field]: value } : option, i === index ? { ...option, [field]: value } : option
), ),
}) })
} }
@@ -119,14 +119,14 @@ export const buttonGroupDefinition: ElementDefinition<
<Input <Input
placeholder="Значение" placeholder="Значение"
value={option.value} value={option.value}
onChange={(e) => onChange={e =>
handleUpdateOption(index, 'value', e.target.value) handleUpdateOption(index, 'value', e.target.value)
} }
/> />
<Input <Input
placeholder="Текст кнопки" placeholder="Текст кнопки"
value={option.label} value={option.label}
onChange={(e) => onChange={e =>
handleUpdateOption(index, 'label', e.target.value) handleUpdateOption(index, 'label', e.target.value)
} }
/> />
@@ -161,8 +161,8 @@ export const buttonGroupDefinition: ElementDefinition<
config.buttonStyle === 'outline' config.buttonStyle === 'outline'
? 'border-border bg-background text-foreground hover:border-primary/30 hover:bg-primary/5' ? 'border-border bg-background text-foreground hover:border-primary/30 hover:bg-primary/5'
: config.buttonStyle === 'ghost' : config.buttonStyle === 'ghost'
? 'border-transparent bg-transparent text-foreground hover:bg-accent hover:text-accent-foreground' ? 'border-transparent bg-transparent text-foreground hover:bg-accent hover:text-accent-foreground'
: 'border-primary bg-primary text-primary-foreground shadow-sm' : 'border-primary bg-primary text-primary-foreground shadow-sm'
}`} }`}
> >
{option.label || `Кнопка ${index + 1}`} {option.label || `Кнопка ${index + 1}`}
@@ -192,10 +192,10 @@ export const buttonGroupDefinition: ElementDefinition<
value === option.value value === option.value
? 'border-primary bg-primary text-primary-foreground shadow-sm' ? 'border-primary bg-primary text-primary-foreground shadow-sm'
: config.buttonStyle === 'outline' : config.buttonStyle === 'outline'
? 'border-border bg-background text-foreground hover:border-primary/30 hover:bg-primary/5' ? 'border-border bg-background text-foreground hover:border-primary/30 hover:bg-primary/5'
: config.buttonStyle === 'ghost' : config.buttonStyle === 'ghost'
? 'border-transparent bg-transparent text-foreground hover:bg-accent hover:text-accent-foreground' ? 'border-transparent bg-transparent text-foreground hover:bg-accent hover:text-accent-foreground'
: 'border-border bg-background text-foreground hover:border-primary/30 hover:bg-primary/5' : 'border-border bg-background text-foreground hover:border-primary/30 hover:bg-primary/5'
}`} }`}
> >
{option.label} {option.label}

View File

@@ -1,7 +1,7 @@
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input' import { Input } from '@/components/ui/input'
import { ElementDefinition } from '@/lib/element-registry' import { ElementDefinition } from '@/lib/element-registry'
import { ElementOption } from '@/types/template' import { ElementOption } from '@/type/template'
import { CheckSquare, Plus, Trash2 } from 'lucide-react' import { CheckSquare, Plus, Trash2 } from 'lucide-react'
interface RadioConfig { interface RadioConfig {
@@ -32,12 +32,12 @@ export const radioDefinition: ElementDefinition<RadioConfig, string> = {
const handleUpdateOption = ( const handleUpdateOption = (
index: number, index: number,
field: 'value' | 'label', field: 'value' | 'label',
value: string, value: string
) => { ) => {
onChange({ onChange({
...config, ...config,
options: config.options.map((option, i) => options: config.options.map((option, i) =>
i === index ? { ...option, [field]: value } : option, i === index ? { ...option, [field]: value } : option
), ),
}) })
} }
@@ -65,14 +65,14 @@ export const radioDefinition: ElementDefinition<RadioConfig, string> = {
<Input <Input
placeholder="Значение" placeholder="Значение"
value={option.value} value={option.value}
onChange={(e) => onChange={e =>
handleUpdateOption(index, 'value', e.target.value) handleUpdateOption(index, 'value', e.target.value)
} }
/> />
<Input <Input
placeholder="Подпись" placeholder="Подпись"
value={option.label} value={option.label}
onChange={(e) => onChange={e =>
handleUpdateOption(index, 'label', e.target.value) handleUpdateOption(index, 'label', e.target.value)
} }
/> />
@@ -121,7 +121,7 @@ export const radioDefinition: ElementDefinition<RadioConfig, string> = {
name="radio-group" name="radio-group"
value={option.value} value={option.value}
checked={value === option.value} checked={value === option.value}
onChange={(e) => onChange?.(e.target.value)} onChange={e => onChange?.(e.target.value)}
required={config.required} required={config.required}
className="h-4 w-4" className="h-4 w-4"
/> />

View File

@@ -8,7 +8,7 @@ import {
SelectValue, SelectValue,
} from '@/components/ui/select' } from '@/components/ui/select'
import { ElementDefinition } from '@/lib/element-registry' import { ElementDefinition } from '@/lib/element-registry'
import { ElementOption } from '@/types/template' import { ElementOption } from '@/type/template'
import { ChevronDown, Plus, Trash2 } from 'lucide-react' import { ChevronDown, Plus, Trash2 } from 'lucide-react'
interface SelectConfig { interface SelectConfig {
@@ -39,12 +39,12 @@ export const selectDefinition: ElementDefinition<SelectConfig, string> = {
const handleUpdateOption = ( const handleUpdateOption = (
index: number, index: number,
field: 'value' | 'label', field: 'value' | 'label',
value: string, value: string
) => { ) => {
onChange({ onChange({
...config, ...config,
options: config.options.map((option, i) => options: config.options.map((option, i) =>
i === index ? { ...option, [field]: value } : option, i === index ? { ...option, [field]: value } : option
), ),
}) })
} }
@@ -72,14 +72,14 @@ export const selectDefinition: ElementDefinition<SelectConfig, string> = {
<Input <Input
placeholder="Значение" placeholder="Значение"
value={option.value} value={option.value}
onChange={(e) => onChange={e =>
handleUpdateOption(index, 'value', e.target.value) handleUpdateOption(index, 'value', e.target.value)
} }
/> />
<Input <Input
placeholder="Подпись" placeholder="Подпись"
value={option.label} value={option.label}
onChange={(e) => onChange={e =>
handleUpdateOption(index, 'label', e.target.value) handleUpdateOption(index, 'label', e.target.value)
} }
/> />

View File

@@ -1,6 +1,6 @@
import { useMultiSheetEngine } from '@/hooks/useMultiSheetEngine' import { useMultiSheetEngine } from '@/hook/useMultiSheetEngine'
import { useSheetAutoSave } from '@/hooks/useSheetAutoSave' import { useSheetAutoSave } from '@/hook/useSheetAutoSave'
import { getFileData, getLatestFileForTemplate } from '@/services/fileApiSevice' import { getFileData, getLatestFileForTemplate } from '@/service/fileApiSevice'
import { produce } from 'immer' import { produce } from 'immer'
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'

View File

@@ -1,4 +1,4 @@
import { MergedCell } from '@/types/template' import { MergedCell } from '@/type/template'
import { CSSProperties } from 'react' import { CSSProperties } from 'react'
import { VariableSizeGrid } from 'react-window' import { VariableSizeGrid } from 'react-window'

View File

@@ -1,4 +1,4 @@
import { MergedCell } from '@/types/template' import { MergedCell } from '@/type/template'
import { MergedCellInfo } from './types' import { MergedCellInfo } from './types'
// --- Helper Functions --- // --- Helper Functions ---

View File

@@ -1,59 +1,73 @@
import { Button } from "@/components/ui/button"; import { Button } from '@/components/ui/button'
import { Input } from "@/components/ui/input"; import { Input } from '@/components/ui/input'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import {
import { CellTarget } from "@/types/template"; Select,
import { Calendar, Settings, Trash2 } from "lucide-react"; SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { CellTarget } from '@/type/template'
import { Calendar, Settings, Trash2 } from 'lucide-react'
interface StandardsConfig { interface StandardsConfig {
registryNumber: string; // ГРСИ registryNumber: string // ГРСИ
sheet: string; // Лист Excel sheet: string // Лист Excel
startCell: string; // Первая ячейка (например "A10") startCell: string // Первая ячейка (например "A10")
maxItems: number; // Обычно 7 maxItems: number // Обычно 7
targetCells: CellTarget[]; targetCells: CellTarget[]
} }
interface StandardsEditorProps { interface StandardsEditorProps {
config: StandardsConfig; config: StandardsConfig
onChange: (config: StandardsConfig) => void; onChange: (config: StandardsConfig) => void
} }
const CellTargetEditor: React.FC<{ const CellTargetEditor: React.FC<{
targets: CellTarget[]; targets: CellTarget[]
onChange: (targets: CellTarget[]) => void; onChange: (targets: CellTarget[]) => void
}> = ({ targets, onChange }) => { }> = ({ targets, onChange }) => {
const handleAddTarget = () => { const handleAddTarget = () => {
onChange([...targets, { sheet: 'L', cell: '', displayName: '' }]); onChange([...targets, { sheet: 'L', cell: '', displayName: '' }])
}; }
const handleUpdateTarget = (index: number, field: keyof CellTarget, value: string) => { const handleUpdateTarget = (
index: number,
field: keyof CellTarget,
value: string
) => {
const updatedTargets = targets.map((target, i) => const updatedTargets = targets.map((target, i) =>
i === index ? { ...target, [field]: value } : target i === index ? { ...target, [field]: value } : target
); )
onChange(updatedTargets); onChange(updatedTargets)
}; }
const handleRemoveTarget = (index: number) => { const handleRemoveTarget = (index: number) => {
onChange(targets.filter((_, i) => i !== index)); onChange(targets.filter((_, i) => i !== index))
}; }
return ( return (
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<label className="text-sm font-medium">Целевые ячейки</label> <label className="text-sm font-medium">Целевые ячейки</label>
<Button variant="outline" size="sm" onClick={handleAddTarget}> <Button variant="outline" size="sm" onClick={handleAddTarget}>
<Calendar className="h-4 w-4 mr-1" /> <Calendar className="mr-1 h-4 w-4" />
Добавить ячейку Добавить ячейку
</Button> </Button>
</div> </div>
<div className="space-y-3 max-h-48 overflow-y-auto"> <div className="max-h-48 space-y-3 overflow-y-auto">
{targets.map((target, index) => ( {targets.map((target, index) => (
<div key={index} className="p-3 border rounded-lg bg-gray-50"> <div key={index} className="rounded-lg border bg-gray-50 p-3">
<div className="grid grid-cols-3 gap-2 mb-2"> <div className="mb-2 grid grid-cols-3 gap-2">
<div className="space-y-1"> <div className="space-y-1">
<label className="text-xs font-medium text-gray-600">Лист</label> <label className="text-xs font-medium text-gray-600">
Лист
</label>
<Select <Select
value={target.sheet} value={target.sheet}
onValueChange={(value) => handleUpdateTarget(index, 'sheet', value)} onValueChange={value =>
handleUpdateTarget(index, 'sheet', value)
}
> >
<SelectTrigger className="h-8"> <SelectTrigger className="h-8">
<SelectValue /> <SelectValue />
@@ -67,20 +81,28 @@ const CellTargetEditor: React.FC<{
</Select> </Select>
</div> </div>
<div className="space-y-1"> <div className="space-y-1">
<label className="text-xs font-medium text-gray-600">Ячейка</label> <label className="text-xs font-medium text-gray-600">
Ячейка
</label>
<Input <Input
placeholder="A1, B5..." placeholder="A1, B5..."
value={target.cell} value={target.cell}
onChange={(e) => handleUpdateTarget(index, 'cell', e.target.value)} onChange={e =>
handleUpdateTarget(index, 'cell', e.target.value)
}
className="h-8" className="h-8"
/> />
</div> </div>
<div className="space-y-1"> <div className="space-y-1">
<label className="text-xs font-medium text-gray-600">Название</label> <label className="text-xs font-medium text-gray-600">
Название
</label>
<Input <Input
placeholder="Описание" placeholder="Описание"
value={target.displayName || ''} value={target.displayName || ''}
onChange={(e) => handleUpdateTarget(index, 'displayName', e.target.value)} onChange={e =>
handleUpdateTarget(index, 'displayName', e.target.value)
}
className="h-8" className="h-8"
/> />
</div> </div>
@@ -91,22 +113,25 @@ const CellTargetEditor: React.FC<{
onClick={() => handleRemoveTarget(index)} onClick={() => handleRemoveTarget(index)}
className="h-6 text-xs text-red-600 hover:text-red-700" className="h-6 text-xs text-red-600 hover:text-red-700"
> >
<Trash2 className="h-3 w-3 mr-1" /> <Trash2 className="mr-1 h-3 w-3" />
Удалить Удалить
</Button> </Button>
</div> </div>
))} ))}
{targets.length === 0 && ( {targets.length === 0 && (
<p className="text-sm text-gray-500 text-center py-4"> <p className="py-4 text-center text-sm text-gray-500">
Добавьте ячейки для связи с элементом Добавьте ячейки для связи с элементом
</p> </p>
)} )}
</div> </div>
</div> </div>
); )
}; }
export const StandardsEditor: React.FC<StandardsEditorProps> = ({ config, onChange }) => { export const StandardsEditor: React.FC<StandardsEditorProps> = ({
config,
onChange,
}) => {
// Обеспечиваем значения по умолчанию // Обеспечиваем значения по умолчанию
const safeConfig = { const safeConfig = {
registryNumber: config.registryNumber || '', registryNumber: config.registryNumber || '',
@@ -114,16 +139,16 @@ export const StandardsEditor: React.FC<StandardsEditorProps> = ({ config, onChan
startCell: config.startCell || 'A1', startCell: config.startCell || 'A1',
maxItems: config.maxItems || 7, maxItems: config.maxItems || 7,
targetCells: config.targetCells || [], targetCells: config.targetCells || [],
}; }
const updateConfig = (updates: Partial<StandardsConfig>) => { const updateConfig = (updates: Partial<StandardsConfig>) => {
onChange({ ...safeConfig, ...updates }); onChange({ ...safeConfig, ...updates })
}; }
return ( return (
<div className="space-y-6"> <div className="space-y-6">
<div className="space-y-2"> <div className="space-y-2">
<label className="text-sm font-medium flex items-center gap-2"> <label className="flex items-center gap-2 text-sm font-medium">
<Settings className="h-4 w-4" /> <Settings className="h-4 w-4" />
Настройки эталонов Настройки эталонов
</label> </label>
@@ -138,7 +163,7 @@ export const StandardsEditor: React.FC<StandardsEditorProps> = ({ config, onChan
<Input <Input
placeholder="Введите номер ГРСИ" placeholder="Введите номер ГРСИ"
value={safeConfig.registryNumber} value={safeConfig.registryNumber}
onChange={(e) => updateConfig({ registryNumber: e.target.value })} onChange={e => updateConfig({ registryNumber: e.target.value })}
/> />
</div> </div>
@@ -146,7 +171,7 @@ export const StandardsEditor: React.FC<StandardsEditorProps> = ({ config, onChan
<label className="text-sm font-medium">Максимум эталонов</label> <label className="text-sm font-medium">Максимум эталонов</label>
<Select <Select
value={safeConfig.maxItems.toString()} value={safeConfig.maxItems.toString()}
onValueChange={(value) => updateConfig({ maxItems: parseInt(value) })} onValueChange={value => updateConfig({ maxItems: parseInt(value) })}
> >
<SelectTrigger> <SelectTrigger>
<SelectValue /> <SelectValue />
@@ -166,7 +191,7 @@ export const StandardsEditor: React.FC<StandardsEditorProps> = ({ config, onChan
<label className="text-sm font-medium">Лист Excel</label> <label className="text-sm font-medium">Лист Excel</label>
<Select <Select
value={safeConfig.sheet} value={safeConfig.sheet}
onValueChange={(value) => updateConfig({ sheet: value })} onValueChange={value => updateConfig({ sheet: value })}
> >
<SelectTrigger> <SelectTrigger>
<SelectValue /> <SelectValue />
@@ -185,7 +210,7 @@ export const StandardsEditor: React.FC<StandardsEditorProps> = ({ config, onChan
<Input <Input
placeholder="A1, B5..." placeholder="A1, B5..."
value={safeConfig.startCell} value={safeConfig.startCell}
onChange={(e) => updateConfig({ startCell: e.target.value })} onChange={e => updateConfig({ startCell: e.target.value })}
/> />
</div> </div>
</div> </div>
@@ -197,23 +222,25 @@ export const StandardsEditor: React.FC<StandardsEditorProps> = ({ config, onChan
</p> </p>
<CellTargetEditor <CellTargetEditor
targets={safeConfig.targetCells} targets={safeConfig.targetCells}
onChange={(targets) => updateConfig({ targetCells: targets })} onChange={targets => updateConfig({ targetCells: targets })}
/> />
</div> </div>
<div className="p-3 bg-blue-50 border border-blue-200 rounded-lg"> <div className="rounded-lg border border-blue-200 bg-blue-50 p-3">
<div className="flex items-start gap-2"> <div className="flex items-start gap-2">
<Calendar className="h-4 w-4 text-blue-600 mt-0.5" /> <Calendar className="mt-0.5 h-4 w-4 text-blue-600" />
<div className="text-sm"> <div className="text-sm">
<div className="font-medium text-blue-800">Автоматическое заполнение</div> <div className="font-medium text-blue-800">
<div className="text-blue-600 text-xs mt-1"> Автоматическое заполнение
При выборе эталонов данные будут записаны в указанные ячейки. </div>
Если выбрано меньше эталонов, чем максимальное количество, <div className="mt-1 text-xs text-blue-600">
оставшиеся ячейки останутся пустыми. При выборе эталонов данные будут записаны в указанные ячейки. Если
выбрано меньше эталонов, чем максимальное количество, оставшиеся
ячейки останутся пустыми.
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
); )
}; }

View File

@@ -1,6 +1,6 @@
import { ElementDefinition } from '@/lib/element-registry' import { ElementDefinition } from '@/lib/element-registry'
import { parseCellAddress } from '@/lib/utils' import { parseCellAddress } from '@/lib/utils'
import { CellTarget } from '@/types/template' import { CellTarget } from '@/type/template'
import { Weight } from 'lucide-react' import { Weight } from 'lucide-react'
import { StandardsEditor } from './StandardsEditor' import { StandardsEditor } from './StandardsEditor'
import { StandardsPreview } from './StandardsPreview' import { StandardsPreview } from './StandardsPreview'
@@ -32,7 +32,7 @@ export const standardsDefinition: ElementDefinition<StandardsConfig, string[]> =
// Пока возвращаем null, так как StandardsDialog требует дополнительные пропсы // Пока возвращаем null, так как StandardsDialog требует дополнительные пропсы
return null return null
}, },
mapToCells: (cfg) => { mapToCells: cfg => {
const res: CellTarget[] = [] const res: CellTarget[] = []
const parsed = parseCellAddress(cfg.startCell) const parsed = parseCellAddress(cfg.startCell)

View File

@@ -13,7 +13,7 @@ import {
FormLayoutSettings, FormLayoutSettings,
Template, Template,
TemplateElement, TemplateElement,
} from '@/types/template' } from '@/type/template'
import { import {
Calendar, Calendar,
Droplets, Droplets,

View File

@@ -1,5 +1,5 @@
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Template } from '@/types/template' import { Template } from '@/type/template'
import { ArrowLeft, FileText, Settings, Wrench } from 'lucide-react' import { ArrowLeft, FileText, Settings, Wrench } from 'lucide-react'
import React from 'react' import React from 'react'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'

View File

@@ -1,5 +1,5 @@
import DualSpreadsheet from '@/components/DualSpreadsheet/DualSpreadsheet' import DualSpreadsheet from '@/components/DualSpreadsheet/DualSpreadsheet'
import { ExcelParseResult } from '@/services/fileApiSevice' import { ExcelParseResult } from '@/service/fileApiSevice'
import React from 'react' import React from 'react'
interface SpreadsheetViewerProps { interface SpreadsheetViewerProps {

View File

@@ -1,7 +1,7 @@
import { FileText, Settings, Wrench } from 'lucide-react' import { FileText, Settings, Wrench } from 'lucide-react'
import React from 'react' import React from 'react'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { Template } from '../../types/template' import { Template } from '../../type/template'
import { Button } from '../ui/button' import { Button } from '../ui/button'
import { Card, CardContent, CardHeader, CardTitle } from '../ui/card' import { Card, CardContent, CardHeader, CardTitle } from '../ui/card'

View File

@@ -1,10 +1,10 @@
import { useTemplateContext } from '@/contexts/TemplateContext' import { useTemplateContext } from '@/context/TemplateContext'
import { import {
getFileData, getFileData,
getLatestFileForTemplate, getLatestFileForTemplate,
parseExcelFile, parseExcelFile,
} from '@/services/fileApiSevice' } from '@/service/fileApiSevice'
import { Template } from '@/types/template' import { Template } from '@/type/template'
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { ExcelUploadPanel } from './ExcelUploadPanel' import { ExcelUploadPanel } from './ExcelUploadPanel'
import { FormattingToolbar } from './FormattingToolbar' import { FormattingToolbar } from './FormattingToolbar'

View File

@@ -1,8 +1,8 @@
import { Circle, CircleCheck, FileText, Plus, Trash2 } from 'lucide-react' import { Circle, CircleCheck, FileText, Plus, Trash2 } from 'lucide-react'
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { useTemplateContext } from '../../contexts/TemplateContext' import { useTemplateContext } from '../../context/TemplateContext'
import { Template } from '../../types/template' import { Template } from '../../type/template'
import { Button } from '../ui/button' import { Button } from '../ui/button'
import { import {
Dialog, Dialog,

View File

@@ -20,7 +20,7 @@ import {
ElementLayout, ElementLayout,
FormLayoutSettings, FormLayoutSettings,
TemplateElement, TemplateElement,
} from '../../types/template' } from '../../type/template'
import { Button } from '../ui/button' import { Button } from '../ui/button'
import { Input } from '../ui/input' import { Input } from '../ui/input'
import { Textarea } from '../ui/textarea' import { Textarea } from '../ui/textarea'

View File

@@ -15,8 +15,8 @@ import {
patchTemplate, patchTemplate,
templateToCreateInput, templateToCreateInput,
templateToPatchInput, templateToPatchInput,
} from '../services/templateApiService' } from '../service/templateApiService'
import { Template, TemplateElement } from '../types/template' import { Template, TemplateElement } from '../type/template'
interface TemplateContextType { interface TemplateContextType {
templates: Template[] templates: Template[]

View File

@@ -4,7 +4,7 @@ import {
createSheet, createSheet,
getSheetsByTemplate, getSheetsByTemplate,
patchSheet, patchSheet,
} from '../services/sheetApiService' } from '../service/sheetApiService'
export interface UseSheetAutoSaveOptions { export interface UseSheetAutoSaveOptions {
templateId?: string templateId?: string

View File

@@ -1,42 +1,48 @@
import { ReactNode } from "react"; import { ReactNode } from 'react'
import { CellTarget, ElementType } from "../types/template"; import { CellTarget, ElementType } from '../type/template'
export interface ElementDefinition<Config = any, Value = any> { export interface ElementDefinition<Config = any, Value = any> {
/* служебные */ /* служебные */
type: ElementType; // "text" | "select" | ... type: ElementType // "text" | "select" | ...
label: string; // Человекочитаемое имя label: string // Человекочитаемое имя
icon: ReactNode; icon: ReactNode
/* React-компоненты */ /* React-компоненты */
Editor: React.FC<{ config: Config; onChange(c: Config): void }>; Editor: React.FC<{ config: Config; onChange(c: Config): void }>
Preview: React.FC<{ config: Config }>; Preview: React.FC<{ config: Config }>
Render: React.FC<{ config: Config; value: Value; onChange?(v: Value): void }>; Render: React.FC<{ config: Config; value: Value; onChange?(v: Value): void }>
/* бизнес-логика */ /* бизнес-логика */
defaultConfig: Config; defaultConfig: Config
mapToCells?(config: Config): CellTarget[]; // куда пишем данные mapToCells?(config: Config): CellTarget[] // куда пишем данные
} }
export const elementRegistry: Record<ElementType, ElementDefinition> = {} as any; export const elementRegistry: Record<ElementType, ElementDefinition> = {} as any
// Функция для регистрации элемента // Функция для регистрации элемента
export function registerElement<T extends ElementType>( export function registerElement<T extends ElementType>(
type: T, type: T,
definition: ElementDefinition definition: ElementDefinition
) { ) {
elementRegistry[type] = definition; elementRegistry[type] = definition
} }
// Получить все доступные типы элементов // Получить все доступные типы элементов
export function getAvailableElementTypes(): Array<{ type: ElementType; label: string; icon: ReactNode }> { export function getAvailableElementTypes(): Array<{
type: ElementType
label: string
icon: ReactNode
}> {
return Object.values(elementRegistry).map(({ type, label, icon }) => ({ return Object.values(elementRegistry).map(({ type, label, icon }) => ({
type, type,
label, label,
icon, icon,
})); }))
} }
// Получить определение элемента по типу // Получить определение элемента по типу
export function getElementDefinition(type: ElementType): ElementDefinition | undefined { export function getElementDefinition(
return elementRegistry[type]; type: ElementType
} ): ElementDefinition | undefined {
return elementRegistry[type]
}

View File

@@ -1,6 +1,11 @@
import { type ClassValue, clsx } from "clsx"; import { type ClassValue, clsx } from 'clsx'
import { twMerge } from "tailwind-merge"; import { twMerge } from 'tailwind-merge'
import { CellTarget, ElementLayout, FormLayoutSettings, TemplateElement } from "../types/template"; import {
CellTarget,
ElementLayout,
FormLayoutSettings,
TemplateElement,
} from '../type/template'
export function cn(...inputs: ClassValue[]) { export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)) return twMerge(clsx(inputs))
@@ -10,21 +15,21 @@ export function cn(...inputs: ClassValue[]) {
export function migrateTemplateElement(element: any): TemplateElement { export function migrateTemplateElement(element: any): TemplateElement {
// Если элемент уже в новом формате // Если элемент уже в новом формате
if (element.targetCells && Array.isArray(element.targetCells)) { if (element.targetCells && Array.isArray(element.targetCells)) {
return element as TemplateElement; return element as TemplateElement
} }
// Миграция из старого формата // Миграция из старого формата
const targetCells: CellTarget[] = []; const targetCells: CellTarget[] = []
if (element.targetCell) { if (element.targetCell) {
// Парсим старый формат targetCell (например "A1", "B5") // Парсим старый формат targetCell (например "A1", "B5")
const cellMatch = element.targetCell.match(/^([A-Z]+)(\d+)$/); const cellMatch = element.targetCell.match(/^([A-Z]+)(\d+)$/)
if (cellMatch) { if (cellMatch) {
targetCells.push({ targetCells.push({
sheet: 'Report', // По умолчанию используем Report sheet: 'Report', // По умолчанию используем Report
cell: element.targetCell, cell: element.targetCell,
displayName: `Ячейка ${element.targetCell}`, displayName: `Ячейка ${element.targetCell}`,
}); })
} }
} }
@@ -33,7 +38,7 @@ export function migrateTemplateElement(element: any): TemplateElement {
targetCells, targetCells,
order: element.order || 0, order: element.order || 0,
layout: element.layout || generateDefaultLayout(element.order || 0), layout: element.layout || generateDefaultLayout(element.order || 0),
} as TemplateElement; } as TemplateElement
} }
// Получение настроек отображения по умолчанию // Получение настроек отображения по умолчанию
@@ -41,49 +46,49 @@ export function getDefaultLayoutSettings(): FormLayoutSettings {
return { return {
gridSize: 20, gridSize: 20,
showGrid: true, showGrid: true,
}; }
} }
// Генерация позиции по умолчанию для нового элемента // Генерация позиции по умолчанию для нового элемента
export function generateDefaultLayout(order: number = 0): ElementLayout { export function generateDefaultLayout(order: number = 0): ElementLayout {
const cols = 4; // Количество колонок для автоматического размещения const cols = 4 // Количество колонок для автоматического размещения
const elementWidth = 300; const elementWidth = 300
const elementHeight = 80; const elementHeight = 80
const padding = 50; const padding = 50
const col = order % cols; const col = order % cols
const row = Math.floor(order / cols); const row = Math.floor(order / cols)
return { return {
x: col * (elementWidth + padding) + padding, x: col * (elementWidth + padding) + padding,
y: row * (elementHeight + padding) + padding, y: row * (elementHeight + padding) + padding,
width: elementWidth, width: elementWidth,
height: elementHeight, height: elementHeight,
zIndex: 1, zIndex: 1,
}; }
} }
// Автоматическое размещение элементов на холсте // Автоматическое размещение элементов на холсте
export function autoArrangeElements( export function autoArrangeElements(
elements: TemplateElement[], elements: TemplateElement[],
gridSize: number = 20 gridSize: number = 20
): TemplateElement[] { ): TemplateElement[] {
const elementWidth = 300; const elementWidth = 300
const elementHeight = 80; const elementHeight = 80
const padding = 50; const padding = 50
const cols = 4; // Фиксированное количество колонок const cols = 4 // Фиксированное количество колонок
return elements.map((element, index) => { return elements.map((element, index) => {
const col = index % cols; const col = index % cols
const row = Math.floor(index / cols); const row = Math.floor(index / cols)
const x = col * (elementWidth + padding) + padding; const x = col * (elementWidth + padding) + padding
const y = row * (elementHeight + padding) + padding; const y = row * (elementHeight + padding) + padding
// Привязка к сетке // Привязка к сетке
const snappedX = Math.round(x / gridSize) * gridSize; const snappedX = Math.round(x / gridSize) * gridSize
const snappedY = Math.round(y / gridSize) * gridSize; const snappedY = Math.round(y / gridSize) * gridSize
return { return {
...element, ...element,
layout: { layout: {
@@ -93,8 +98,8 @@ export function autoArrangeElements(
height: elementHeight, height: elementHeight,
zIndex: 1, zIndex: 1,
}, },
}; }
}); })
} }
// Проверка пересечения элементов // Проверка пересечения элементов
@@ -108,7 +113,7 @@ export function checkElementOverlap(
element2.x + element2.width + threshold < element1.x || element2.x + element2.width + threshold < element1.x ||
element1.y + element1.height + threshold < element2.y || element1.y + element1.height + threshold < element2.y ||
element2.y + element2.height + threshold < element1.y element2.y + element2.height + threshold < element1.y
); )
} }
// Поиск свободного места для элемента (поиск в разумных пределах) // Поиск свободного места для элемента (поиск в разумных пределах)
@@ -117,54 +122,56 @@ export function findFreePosition(
existingElements: ElementLayout[], existingElements: ElementLayout[],
gridSize: number = 20 gridSize: number = 20
): ElementLayout { ): ElementLayout {
const step = gridSize; const step = gridSize
const maxWidth = 1600; // Максимальная ширина поиска const maxWidth = 1600 // Максимальная ширина поиска
const maxHeight = 1200; // Максимальная высота поиска const maxHeight = 1200 // Максимальная высота поиска
const maxAttempts = 500; const maxAttempts = 500
let attempts = 0; let attempts = 0
for (let y = 50; y <= maxHeight - newElement.height; y += step) { for (let y = 50; y <= maxHeight - newElement.height; y += step) {
for (let x = 50; x <= maxWidth - newElement.width; x += step) { for (let x = 50; x <= maxWidth - newElement.width; x += step) {
attempts++; attempts++
if (attempts > maxAttempts) break; if (attempts > maxAttempts) break
const testPosition = { ...newElement, x, y }; const testPosition = { ...newElement, x, y }
const hasOverlap = existingElements.some(existing => const hasOverlap = existingElements.some(existing =>
checkElementOverlap(testPosition, existing) checkElementOverlap(testPosition, existing)
); )
if (!hasOverlap) { if (!hasOverlap) {
return testPosition; return testPosition
} }
} }
if (attempts > maxAttempts) break; if (attempts > maxAttempts) break
} }
// Если не нашли свободное место, размещаем в правом нижнем углу // Если не нашли свободное место, размещаем в правом нижнем углу
return { return {
...newElement, ...newElement,
x: 50, x: 50,
y: existingElements.length * 100 + 50 y: existingElements.length * 100 + 50,
}; }
} }
// Валидация адреса ячейки // Валидация адреса ячейки
export function validateCellAddress(cell: string): boolean { export function validateCellAddress(cell: string): boolean {
return /^[A-Z]+\d+$/.test(cell); return /^[A-Z]+\d+$/.test(cell)
} }
// Парсинг адреса ячейки в компоненты // Парсинг адреса ячейки в компоненты
export function parseCellAddress(cell: string): { column: string; row: number } | null { export function parseCellAddress(
const match = cell.match(/^([A-Z]+)(\d+)$/); cell: string
if (!match) return null; ): { column: string; row: number } | null {
const match = cell.match(/^([A-Z]+)(\d+)$/)
if (!match) return null
return { return {
column: match[1], column: match[1],
row: parseInt(match[2], 10), row: parseInt(match[2], 10),
}; }
} }
// Генерация уникального ID для элемента // Генерация уникального ID для элемента
export function generateElementId(): string { export function generateElementId(): string {
return `element_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; return `element_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
} }

View File

@@ -1,7 +1,7 @@
import { ElementConstructor } from '@/components/TemplateManager/ElementConstructor' import { ElementConstructor } from '@/components/TemplateManager/ElementConstructor'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { useTemplateContext } from '@/contexts/TemplateContext' import { useTemplateContext } from '@/context/TemplateContext'
import { Template, TemplateElement } from '@/types/template' import { Template, TemplateElement } from '@/type/template'
import { ArrowLeft, FileText, Settings, Wrench } from 'lucide-react' import { ArrowLeft, FileText, Settings, Wrench } from 'lucide-react'
import { FC, useEffect, useState } from 'react' import { FC, useEffect, useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom' import { useNavigate, useParams } from 'react-router-dom'

View File

@@ -14,10 +14,10 @@ import {
SelectValue, SelectValue,
} from '@/components/ui/select' } from '@/components/ui/select'
import { Textarea } from '@/components/ui/textarea' import { Textarea } from '@/components/ui/textarea'
import { useTemplateContext } from '@/contexts/TemplateContext' import { useTemplateContext } from '@/context/TemplateContext'
import { getElementDefinition } from '@/lib/element-registry' import { getElementDefinition } from '@/lib/element-registry'
import { getLatestFileForTemplate } from '@/services/fileApiSevice' import { getLatestFileForTemplate } from '@/service/fileApiSevice'
import { Template, TemplateElement } from '@/types/template' import { Template, TemplateElement } from '@/type/template'
import { import {
ArrowLeft, ArrowLeft,
Download, Download,

View File

@@ -1,5 +1,5 @@
import { TemplateManager } from '@/components/TemplateManager/TemplateManager' import { TemplateManager } from '@/components/TemplateManager/TemplateManager'
import { useTemplateContext } from '@/contexts/TemplateContext' import { useTemplateContext } from '@/context/TemplateContext'
import { FC, useEffect } from 'react' import { FC, useEffect } from 'react'
import { useParams } from 'react-router-dom' import { useParams } from 'react-router-dom'

View File

@@ -38,7 +38,7 @@ export interface ApiTemplate {
// Импортируем типы для преобразования // Импортируем типы для преобразования
import { getDefaultLayoutSettings, migrateTemplateElement } from '../lib/utils' import { getDefaultLayoutSettings, migrateTemplateElement } from '../lib/utils'
import { Template, TemplateElement } from '../types/template' import { Template, TemplateElement } from '../type/template'
// Утилитарные функции для преобразования данных // Утилитарные функции для преобразования данных

View File

@@ -8,12 +8,13 @@
"moduleResolution": "node", "moduleResolution": "node",
"esModuleInterop": true, "esModuleInterop": true,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"strictNullChecks": true, "strictNullChecks": true,
"outDir": "./dist/", "outDir": "./dist/",
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"@/*": ["./src/*"], "@/*": ["./src/*"]
} }
}, },
"ts-node": { "ts-node": {
@@ -22,4 +23,4 @@
} }
}, },
"exclude": ["node_modules", "dist", "build", "coverage"] "exclude": ["node_modules", "dist", "build", "coverage"]
} }

View File

@@ -7,7 +7,7 @@ export default defineConfig({
plugins: [react()], plugins: [react()],
resolve: { resolve: {
alias: { alias: {
'@': path.resolve('src'), '@': path.resolve(__dirname, 'src'),
'@widgets': path.resolve('src/widgets'), '@widgets': path.resolve('src/widgets'),
'@features': path.resolve('src/features'), '@features': path.resolve('src/features'),
'@entities': path.resolve('src/entities'), '@entities': path.resolve('src/entities'),

View File

@@ -840,6 +840,13 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
"@types/node@^24.0.15":
version "24.0.15"
resolved "https://registry.yarnpkg.com/@types/node/-/node-24.0.15.tgz#f34fbc973e7d64217106e0c59ed8761e6b51381e"
integrity sha512-oaeTSbCef7U/z7rDeJA138xpG3NuKc64/rZ2qmUFkFJmnMsAPaluIifqyWd8hSSMxyP9oie3dLAqYPblag9KgA==
dependencies:
undici-types "~7.8.0"
"@types/prop-types@*": "@types/prop-types@*":
version "15.7.15" version "15.7.15"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.15.tgz#e6e5a86d602beaca71ce5163fadf5f95d70931c7" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.15.tgz#e6e5a86d602beaca71ce5163fadf5f95d70931c7"
@@ -1156,11 +1163,6 @@ acorn@^8.9.0:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816"
integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==
adler-32@~1.3.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/adler-32/-/adler-32-1.3.1.tgz#1dbf0b36dda0012189a32b3679061932df1821e2"
integrity sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==
ajv@^6.12.4: ajv@^6.12.4:
version "6.12.6" version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
@@ -1418,14 +1420,6 @@ caniuse-lite@^1.0.30001702, caniuse-lite@^1.0.30001726:
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz#22e9706422ad37aa50556af8c10e40e2d93a8b85" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz#22e9706422ad37aa50556af8c10e40e2d93a8b85"
integrity sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q== integrity sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==
cfb@~1.2.1:
version "1.2.2"
resolved "https://registry.yarnpkg.com/cfb/-/cfb-1.2.2.tgz#94e687628c700e5155436dac05f74e08df23bc44"
integrity sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==
dependencies:
adler-32 "~1.3.0"
crc-32 "~1.2.0"
chalk@^1.1.3: chalk@^1.1.3:
version "1.1.3" version "1.1.3"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
@@ -1477,11 +1471,6 @@ clsx@^2.1.1:
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999"
integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
codepage@~1.15.0:
version "1.15.0"
resolved "https://registry.yarnpkg.com/codepage/-/codepage-1.15.0.tgz#2e00519024b39424ec66eeb3ec07227e692618ab"
integrity sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==
color-convert@^2.0.1: color-convert@^2.0.1:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
@@ -1514,11 +1503,6 @@ concat-map@0.0.1:
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
crc-32@~1.2.0, crc-32@~1.2.1:
version "1.2.2"
resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff"
integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==
cross-spawn@^7.0.2, cross-spawn@^7.0.6: cross-spawn@^7.0.2, cross-spawn@^7.0.6:
version "7.0.6" version "7.0.6"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
@@ -2113,11 +2097,6 @@ foreground-child@^3.1.0:
cross-spawn "^7.0.6" cross-spawn "^7.0.6"
signal-exit "^4.0.1" signal-exit "^4.0.1"
frac@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/frac/-/frac-1.1.2.tgz#3d74f7f6478c88a1b5020306d747dc6313c74d0b"
integrity sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==
fraction.js@^4.3.7: fraction.js@^4.3.7:
version "4.3.7" version "4.3.7"
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7"
@@ -3563,13 +3542,6 @@ source-map-js@^1.2.1:
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
ssf@~0.11.2:
version "0.11.2"
resolved "https://registry.yarnpkg.com/ssf/-/ssf-0.11.2.tgz#0b99698b237548d088fc43cdf2b70c1a7512c06c"
integrity sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==
dependencies:
frac "~1.1.2"
stable-hash-x@^0.2.0: stable-hash-x@^0.2.0:
version "0.2.0" version "0.2.0"
resolved "https://registry.yarnpkg.com/stable-hash-x/-/stable-hash-x-0.2.0.tgz#dfd76bfa5d839a7470125c6a6b3c8b22061793e9" resolved "https://registry.yarnpkg.com/stable-hash-x/-/stable-hash-x-0.2.0.tgz#dfd76bfa5d839a7470125c6a6b3c8b22061793e9"
@@ -3896,6 +3868,11 @@ unbox-primitive@^1.1.0:
has-symbols "^1.1.0" has-symbols "^1.1.0"
which-boxed-primitive "^1.1.1" which-boxed-primitive "^1.1.1"
undici-types@~7.8.0:
version "7.8.0"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.8.0.tgz#de00b85b710c54122e44fbfd911f8d70174cd294"
integrity sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==
unrs-resolver@^1.7.11: unrs-resolver@^1.7.11:
version "1.11.1" version "1.11.1"
resolved "https://registry.yarnpkg.com/unrs-resolver/-/unrs-resolver-1.11.1.tgz#be9cd8686c99ef53ecb96df2a473c64d304048a9" resolved "https://registry.yarnpkg.com/unrs-resolver/-/unrs-resolver-1.11.1.tgz#be9cd8686c99ef53ecb96df2a473c64d304048a9"
@@ -4059,21 +4036,11 @@ which@^2.0.1:
dependencies: dependencies:
isexe "^2.0.0" isexe "^2.0.0"
wmf@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wmf/-/wmf-1.0.2.tgz#7d19d621071a08c2bdc6b7e688a9c435298cc2da"
integrity sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==
word-wrap@^1.2.5: word-wrap@^1.2.5:
version "1.2.5" version "1.2.5"
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
word@~0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/word/-/word-0.3.0.tgz#8542157e4f8e849f4a363a288992d47612db9961"
integrity sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
version "7.0.0" version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
@@ -4097,19 +4064,6 @@ wrappy@1:
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
xlsx@^0.18.5:
version "0.18.5"
resolved "https://registry.yarnpkg.com/xlsx/-/xlsx-0.18.5.tgz#16711b9113c848076b8a177022799ad356eba7d0"
integrity sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==
dependencies:
adler-32 "~1.3.0"
cfb "~1.2.1"
codepage "~1.15.0"
crc-32 "~1.2.1"
ssf "~0.11.2"
wmf "~1.0.1"
word "~0.3.0"
yaml@^2.3.4: yaml@^2.3.4:
version "2.8.0" version "2.8.0"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.0.tgz#15f8c9866211bdc2d3781a0890e44d4fa1a5fff6" resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.0.tgz#15f8c9866211bdc2d3781a0890e44d4fa1a5fff6"