переименования папок
This commit is contained in:
@@ -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",
|
||||||
|
|||||||
@@ -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 = () => {
|
||||||
|
|||||||
@@ -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}
|
||||||
@@ -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"
|
||||||
/>
|
/>
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -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'
|
||||||
|
|
||||||
@@ -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'
|
||||||
|
|
||||||
@@ -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 ---
|
||||||
@@ -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>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
@@ -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)
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
FormLayoutSettings,
|
FormLayoutSettings,
|
||||||
Template,
|
Template,
|
||||||
TemplateElement,
|
TemplateElement,
|
||||||
} from '@/types/template'
|
} from '@/type/template'
|
||||||
import {
|
import {
|
||||||
Calendar,
|
Calendar,
|
||||||
Droplets,
|
Droplets,
|
||||||
@@ -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'
|
||||||
@@ -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 {
|
||||||
@@ -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'
|
||||||
|
|
||||||
@@ -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'
|
||||||
@@ -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,
|
||||||
@@ -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'
|
||||||
@@ -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[]
|
||||||
@@ -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
|
||||||
@@ -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]
|
||||||
}
|
}
|
||||||
103
src/lib/utils.ts
103
src/lib/utils.ts
@@ -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,18 +46,18 @@ 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,
|
||||||
@@ -60,7 +65,7 @@ export function generateDefaultLayout(order: number = 0): ElementLayout {
|
|||||||
width: elementWidth,
|
width: elementWidth,
|
||||||
height: elementHeight,
|
height: elementHeight,
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Автоматическое размещение элементов на холсте
|
// Автоматическое размещение элементов на холсте
|
||||||
@@ -68,21 +73,21 @@ 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,
|
||||||
@@ -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)}`
|
||||||
}
|
}
|
||||||
@@ -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'
|
||||||
@@ -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,
|
||||||
@@ -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'
|
||||||
|
|
||||||
@@ -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'
|
||||||
|
|
||||||
// Утилитарные функции для преобразования данных
|
// Утилитарные функции для преобразования данных
|
||||||
|
|
||||||
@@ -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": {
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
|||||||
70
yarn.lock
70
yarn.lock
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user