переименования папок
This commit is contained in:
@@ -43,6 +43,7 @@
|
||||
"tailwind-merge": "^3.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.0.15",
|
||||
"@types/react": "^18.0.37",
|
||||
"@types/react-beautiful-dnd": "^13.1.8",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
|
||||
@@ -2,10 +2,10 @@ import { FC } from 'react'
|
||||
|
||||
import { Navigate, Route, Routes } from 'react-router-dom'
|
||||
|
||||
import { TemplateProvider } from '../contexts/TemplateContext'
|
||||
import { ElementsCreation } from '../pages/ElementsCreation'
|
||||
import { ProtocolCreation } from '../pages/ProtocolCreation'
|
||||
import { TemplateSetup } from '../pages/TemplateSetup'
|
||||
import { TemplateProvider } from '../context/TemplateContext'
|
||||
import { ElementsCreation } from '../page/ElementsCreation'
|
||||
import { ProtocolCreation } from '../page/ProtocolCreation'
|
||||
import { TemplateSetup } from '../page/TemplateSetup'
|
||||
import { Layout } from './Layout'
|
||||
|
||||
const App: FC = () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { ElementDefinition } from '@/lib/element-registry'
|
||||
import { ElementOption } from '@/types/template'
|
||||
import { ElementOption } from '@/type/template'
|
||||
import { Plus, Square, Trash2 } from 'lucide-react'
|
||||
|
||||
interface ButtonGroupConfig {
|
||||
@@ -39,12 +39,12 @@ export const buttonGroupDefinition: ElementDefinition<
|
||||
const handleUpdateOption = (
|
||||
index: number,
|
||||
field: 'value' | 'label',
|
||||
value: string,
|
||||
value: string
|
||||
) => {
|
||||
onChange({
|
||||
...config,
|
||||
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
|
||||
placeholder="Значение"
|
||||
value={option.value}
|
||||
onChange={(e) =>
|
||||
onChange={e =>
|
||||
handleUpdateOption(index, 'value', e.target.value)
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
placeholder="Текст кнопки"
|
||||
value={option.label}
|
||||
onChange={(e) =>
|
||||
onChange={e =>
|
||||
handleUpdateOption(index, 'label', e.target.value)
|
||||
}
|
||||
/>
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { ElementDefinition } from '@/lib/element-registry'
|
||||
import { ElementOption } from '@/types/template'
|
||||
import { ElementOption } from '@/type/template'
|
||||
import { CheckSquare, Plus, Trash2 } from 'lucide-react'
|
||||
|
||||
interface RadioConfig {
|
||||
@@ -32,12 +32,12 @@ export const radioDefinition: ElementDefinition<RadioConfig, string> = {
|
||||
const handleUpdateOption = (
|
||||
index: number,
|
||||
field: 'value' | 'label',
|
||||
value: string,
|
||||
value: string
|
||||
) => {
|
||||
onChange({
|
||||
...config,
|
||||
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
|
||||
placeholder="Значение"
|
||||
value={option.value}
|
||||
onChange={(e) =>
|
||||
onChange={e =>
|
||||
handleUpdateOption(index, 'value', e.target.value)
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
placeholder="Подпись"
|
||||
value={option.label}
|
||||
onChange={(e) =>
|
||||
onChange={e =>
|
||||
handleUpdateOption(index, 'label', e.target.value)
|
||||
}
|
||||
/>
|
||||
@@ -121,7 +121,7 @@ export const radioDefinition: ElementDefinition<RadioConfig, string> = {
|
||||
name="radio-group"
|
||||
value={option.value}
|
||||
checked={value === option.value}
|
||||
onChange={(e) => onChange?.(e.target.value)}
|
||||
onChange={e => onChange?.(e.target.value)}
|
||||
required={config.required}
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { ElementDefinition } from '@/lib/element-registry'
|
||||
import { ElementOption } from '@/types/template'
|
||||
import { ElementOption } from '@/type/template'
|
||||
import { ChevronDown, Plus, Trash2 } from 'lucide-react'
|
||||
|
||||
interface SelectConfig {
|
||||
@@ -39,12 +39,12 @@ export const selectDefinition: ElementDefinition<SelectConfig, string> = {
|
||||
const handleUpdateOption = (
|
||||
index: number,
|
||||
field: 'value' | 'label',
|
||||
value: string,
|
||||
value: string
|
||||
) => {
|
||||
onChange({
|
||||
...config,
|
||||
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
|
||||
placeholder="Значение"
|
||||
value={option.value}
|
||||
onChange={(e) =>
|
||||
onChange={e =>
|
||||
handleUpdateOption(index, 'value', e.target.value)
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
placeholder="Подпись"
|
||||
value={option.label}
|
||||
onChange={(e) =>
|
||||
onChange={e =>
|
||||
handleUpdateOption(index, 'label', e.target.value)
|
||||
}
|
||||
/>
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMultiSheetEngine } from '@/hooks/useMultiSheetEngine'
|
||||
import { useSheetAutoSave } from '@/hooks/useSheetAutoSave'
|
||||
import { getFileData, getLatestFileForTemplate } from '@/services/fileApiSevice'
|
||||
import { useMultiSheetEngine } from '@/hook/useMultiSheetEngine'
|
||||
import { useSheetAutoSave } from '@/hook/useSheetAutoSave'
|
||||
import { getFileData, getLatestFileForTemplate } from '@/service/fileApiSevice'
|
||||
import { produce } from 'immer'
|
||||
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 { VariableSizeGrid } from 'react-window'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { MergedCell } from '@/types/template'
|
||||
import { MergedCell } from '@/type/template'
|
||||
import { MergedCellInfo } from './types'
|
||||
|
||||
// --- Helper Functions ---
|
||||
@@ -1,59 +1,73 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { CellTarget } from "@/types/template";
|
||||
import { Calendar, Settings, Trash2 } from "lucide-react";
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { CellTarget } from '@/type/template'
|
||||
import { Calendar, Settings, Trash2 } from 'lucide-react'
|
||||
|
||||
interface StandardsConfig {
|
||||
registryNumber: string; // ГРСИ
|
||||
sheet: string; // Лист Excel
|
||||
startCell: string; // Первая ячейка (например "A10")
|
||||
maxItems: number; // Обычно 7
|
||||
targetCells: CellTarget[];
|
||||
registryNumber: string // ГРСИ
|
||||
sheet: string // Лист Excel
|
||||
startCell: string // Первая ячейка (например "A10")
|
||||
maxItems: number // Обычно 7
|
||||
targetCells: CellTarget[]
|
||||
}
|
||||
|
||||
interface StandardsEditorProps {
|
||||
config: StandardsConfig;
|
||||
onChange: (config: StandardsConfig) => void;
|
||||
config: StandardsConfig
|
||||
onChange: (config: StandardsConfig) => void
|
||||
}
|
||||
|
||||
const CellTargetEditor: React.FC<{
|
||||
targets: CellTarget[];
|
||||
onChange: (targets: CellTarget[]) => void;
|
||||
targets: CellTarget[]
|
||||
onChange: (targets: CellTarget[]) => void
|
||||
}> = ({ targets, onChange }) => {
|
||||
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) =>
|
||||
i === index ? { ...target, [field]: value } : target
|
||||
);
|
||||
onChange(updatedTargets);
|
||||
};
|
||||
)
|
||||
onChange(updatedTargets)
|
||||
}
|
||||
|
||||
const handleRemoveTarget = (index: number) => {
|
||||
onChange(targets.filter((_, i) => i !== index));
|
||||
};
|
||||
onChange(targets.filter((_, i) => i !== index))
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-sm font-medium">Целевые ячейки</label>
|
||||
<Button variant="outline" size="sm" onClick={handleAddTarget}>
|
||||
<Calendar className="h-4 w-4 mr-1" />
|
||||
<Calendar className="mr-1 h-4 w-4" />
|
||||
Добавить ячейку
|
||||
</Button>
|
||||
</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) => (
|
||||
<div key={index} className="p-3 border rounded-lg bg-gray-50">
|
||||
<div className="grid grid-cols-3 gap-2 mb-2">
|
||||
<div key={index} className="rounded-lg border bg-gray-50 p-3">
|
||||
<div className="mb-2 grid grid-cols-3 gap-2">
|
||||
<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
|
||||
value={target.sheet}
|
||||
onValueChange={(value) => handleUpdateTarget(index, 'sheet', value)}
|
||||
onValueChange={value =>
|
||||
handleUpdateTarget(index, 'sheet', value)
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="h-8">
|
||||
<SelectValue />
|
||||
@@ -67,20 +81,28 @@ const CellTargetEditor: React.FC<{
|
||||
</Select>
|
||||
</div>
|
||||
<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
|
||||
placeholder="A1, B5..."
|
||||
value={target.cell}
|
||||
onChange={(e) => handleUpdateTarget(index, 'cell', e.target.value)}
|
||||
onChange={e =>
|
||||
handleUpdateTarget(index, 'cell', e.target.value)
|
||||
}
|
||||
className="h-8"
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
placeholder="Описание"
|
||||
value={target.displayName || ''}
|
||||
onChange={(e) => handleUpdateTarget(index, 'displayName', e.target.value)}
|
||||
onChange={e =>
|
||||
handleUpdateTarget(index, 'displayName', e.target.value)
|
||||
}
|
||||
className="h-8"
|
||||
/>
|
||||
</div>
|
||||
@@ -91,22 +113,25 @@ const CellTargetEditor: React.FC<{
|
||||
onClick={() => handleRemoveTarget(index)}
|
||||
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>
|
||||
</div>
|
||||
))}
|
||||
{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>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export const StandardsEditor: React.FC<StandardsEditorProps> = ({ config, onChange }) => {
|
||||
export const StandardsEditor: React.FC<StandardsEditorProps> = ({
|
||||
config,
|
||||
onChange,
|
||||
}) => {
|
||||
// Обеспечиваем значения по умолчанию
|
||||
const safeConfig = {
|
||||
registryNumber: config.registryNumber || '',
|
||||
@@ -114,16 +139,16 @@ export const StandardsEditor: React.FC<StandardsEditorProps> = ({ config, onChan
|
||||
startCell: config.startCell || 'A1',
|
||||
maxItems: config.maxItems || 7,
|
||||
targetCells: config.targetCells || [],
|
||||
};
|
||||
}
|
||||
|
||||
const updateConfig = (updates: Partial<StandardsConfig>) => {
|
||||
onChange({ ...safeConfig, ...updates });
|
||||
};
|
||||
onChange({ ...safeConfig, ...updates })
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<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" />
|
||||
Настройки эталонов
|
||||
</label>
|
||||
@@ -138,7 +163,7 @@ export const StandardsEditor: React.FC<StandardsEditorProps> = ({ config, onChan
|
||||
<Input
|
||||
placeholder="Введите номер ГРСИ"
|
||||
value={safeConfig.registryNumber}
|
||||
onChange={(e) => updateConfig({ registryNumber: e.target.value })}
|
||||
onChange={e => updateConfig({ registryNumber: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -146,7 +171,7 @@ export const StandardsEditor: React.FC<StandardsEditorProps> = ({ config, onChan
|
||||
<label className="text-sm font-medium">Максимум эталонов</label>
|
||||
<Select
|
||||
value={safeConfig.maxItems.toString()}
|
||||
onValueChange={(value) => updateConfig({ maxItems: parseInt(value) })}
|
||||
onValueChange={value => updateConfig({ maxItems: parseInt(value) })}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
@@ -166,7 +191,7 @@ export const StandardsEditor: React.FC<StandardsEditorProps> = ({ config, onChan
|
||||
<label className="text-sm font-medium">Лист Excel</label>
|
||||
<Select
|
||||
value={safeConfig.sheet}
|
||||
onValueChange={(value) => updateConfig({ sheet: value })}
|
||||
onValueChange={value => updateConfig({ sheet: value })}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
@@ -185,7 +210,7 @@ export const StandardsEditor: React.FC<StandardsEditorProps> = ({ config, onChan
|
||||
<Input
|
||||
placeholder="A1, B5..."
|
||||
value={safeConfig.startCell}
|
||||
onChange={(e) => updateConfig({ startCell: e.target.value })}
|
||||
onChange={e => updateConfig({ startCell: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -197,23 +222,25 @@ export const StandardsEditor: React.FC<StandardsEditorProps> = ({ config, onChan
|
||||
</p>
|
||||
<CellTargetEditor
|
||||
targets={safeConfig.targetCells}
|
||||
onChange={(targets) => updateConfig({ targetCells: targets })}
|
||||
onChange={targets => updateConfig({ targetCells: targets })}
|
||||
/>
|
||||
</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">
|
||||
<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="font-medium text-blue-800">Автоматическое заполнение</div>
|
||||
<div className="text-blue-600 text-xs mt-1">
|
||||
При выборе эталонов данные будут записаны в указанные ячейки.
|
||||
Если выбрано меньше эталонов, чем максимальное количество,
|
||||
оставшиеся ячейки останутся пустыми.
|
||||
<div className="font-medium text-blue-800">
|
||||
Автоматическое заполнение
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-blue-600">
|
||||
При выборе эталонов данные будут записаны в указанные ячейки. Если
|
||||
выбрано меньше эталонов, чем максимальное количество, оставшиеся
|
||||
ячейки останутся пустыми.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ElementDefinition } from '@/lib/element-registry'
|
||||
import { parseCellAddress } from '@/lib/utils'
|
||||
import { CellTarget } from '@/types/template'
|
||||
import { CellTarget } from '@/type/template'
|
||||
import { Weight } from 'lucide-react'
|
||||
import { StandardsEditor } from './StandardsEditor'
|
||||
import { StandardsPreview } from './StandardsPreview'
|
||||
@@ -32,7 +32,7 @@ export const standardsDefinition: ElementDefinition<StandardsConfig, string[]> =
|
||||
// Пока возвращаем null, так как StandardsDialog требует дополнительные пропсы
|
||||
return null
|
||||
},
|
||||
mapToCells: (cfg) => {
|
||||
mapToCells: cfg => {
|
||||
const res: CellTarget[] = []
|
||||
const parsed = parseCellAddress(cfg.startCell)
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
FormLayoutSettings,
|
||||
Template,
|
||||
TemplateElement,
|
||||
} from '@/types/template'
|
||||
} from '@/type/template'
|
||||
import {
|
||||
Calendar,
|
||||
Droplets,
|
||||
@@ -1,5 +1,5 @@
|
||||
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 React from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
@@ -1,5 +1,5 @@
|
||||
import DualSpreadsheet from '@/components/DualSpreadsheet/DualSpreadsheet'
|
||||
import { ExcelParseResult } from '@/services/fileApiSevice'
|
||||
import { ExcelParseResult } from '@/service/fileApiSevice'
|
||||
import React from 'react'
|
||||
|
||||
interface SpreadsheetViewerProps {
|
||||
@@ -1,7 +1,7 @@
|
||||
import { FileText, Settings, Wrench } from 'lucide-react'
|
||||
import React from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { Template } from '../../types/template'
|
||||
import { Template } from '../../type/template'
|
||||
import { Button } from '../ui/button'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '../ui/card'
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { useTemplateContext } from '@/contexts/TemplateContext'
|
||||
import { useTemplateContext } from '@/context/TemplateContext'
|
||||
import {
|
||||
getFileData,
|
||||
getLatestFileForTemplate,
|
||||
parseExcelFile,
|
||||
} from '@/services/fileApiSevice'
|
||||
import { Template } from '@/types/template'
|
||||
} from '@/service/fileApiSevice'
|
||||
import { Template } from '@/type/template'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { ExcelUploadPanel } from './ExcelUploadPanel'
|
||||
import { FormattingToolbar } from './FormattingToolbar'
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Circle, CircleCheck, FileText, Plus, Trash2 } from 'lucide-react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useTemplateContext } from '../../contexts/TemplateContext'
|
||||
import { Template } from '../../types/template'
|
||||
import { useTemplateContext } from '../../context/TemplateContext'
|
||||
import { Template } from '../../type/template'
|
||||
import { Button } from '../ui/button'
|
||||
import {
|
||||
Dialog,
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
ElementLayout,
|
||||
FormLayoutSettings,
|
||||
TemplateElement,
|
||||
} from '../../types/template'
|
||||
} from '../../type/template'
|
||||
import { Button } from '../ui/button'
|
||||
import { Input } from '../ui/input'
|
||||
import { Textarea } from '../ui/textarea'
|
||||
@@ -15,8 +15,8 @@ import {
|
||||
patchTemplate,
|
||||
templateToCreateInput,
|
||||
templateToPatchInput,
|
||||
} from '../services/templateApiService'
|
||||
import { Template, TemplateElement } from '../types/template'
|
||||
} from '../service/templateApiService'
|
||||
import { Template, TemplateElement } from '../type/template'
|
||||
|
||||
interface TemplateContextType {
|
||||
templates: Template[]
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
createSheet,
|
||||
getSheetsByTemplate,
|
||||
patchSheet,
|
||||
} from '../services/sheetApiService'
|
||||
} from '../service/sheetApiService'
|
||||
|
||||
export interface UseSheetAutoSaveOptions {
|
||||
templateId?: string
|
||||
@@ -1,42 +1,48 @@
|
||||
import { ReactNode } from "react";
|
||||
import { CellTarget, ElementType } from "../types/template";
|
||||
import { ReactNode } from 'react'
|
||||
import { CellTarget, ElementType } from '../type/template'
|
||||
|
||||
export interface ElementDefinition<Config = any, Value = any> {
|
||||
/* служебные */
|
||||
type: ElementType; // "text" | "select" | ...
|
||||
label: string; // Человекочитаемое имя
|
||||
icon: ReactNode;
|
||||
type: ElementType // "text" | "select" | ...
|
||||
label: string // Человекочитаемое имя
|
||||
icon: ReactNode
|
||||
|
||||
/* React-компоненты */
|
||||
Editor: React.FC<{ config: Config; onChange(c: Config): void }>;
|
||||
Preview: React.FC<{ config: Config }>;
|
||||
Render: React.FC<{ config: Config; value: Value; onChange?(v: Value): void }>;
|
||||
Editor: React.FC<{ config: Config; onChange(c: Config): void }>
|
||||
Preview: React.FC<{ config: Config }>
|
||||
Render: React.FC<{ config: Config; value: Value; onChange?(v: Value): void }>
|
||||
|
||||
/* бизнес-логика */
|
||||
defaultConfig: Config;
|
||||
mapToCells?(config: Config): CellTarget[]; // куда пишем данные
|
||||
defaultConfig: Config
|
||||
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>(
|
||||
type: T,
|
||||
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 }) => ({
|
||||
type,
|
||||
label,
|
||||
icon,
|
||||
}));
|
||||
}))
|
||||
}
|
||||
|
||||
// Получить определение элемента по типу
|
||||
export function getElementDefinition(type: ElementType): ElementDefinition | undefined {
|
||||
return elementRegistry[type];
|
||||
export function getElementDefinition(
|
||||
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 { twMerge } from "tailwind-merge";
|
||||
import { CellTarget, ElementLayout, FormLayoutSettings, TemplateElement } from "../types/template";
|
||||
import { type ClassValue, clsx } from 'clsx'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import {
|
||||
CellTarget,
|
||||
ElementLayout,
|
||||
FormLayoutSettings,
|
||||
TemplateElement,
|
||||
} from '../type/template'
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
@@ -10,21 +15,21 @@ export function cn(...inputs: ClassValue[]) {
|
||||
export function migrateTemplateElement(element: any): TemplateElement {
|
||||
// Если элемент уже в новом формате
|
||||
if (element.targetCells && Array.isArray(element.targetCells)) {
|
||||
return element as TemplateElement;
|
||||
return element as TemplateElement
|
||||
}
|
||||
|
||||
// Миграция из старого формата
|
||||
const targetCells: CellTarget[] = [];
|
||||
const targetCells: CellTarget[] = []
|
||||
|
||||
if (element.targetCell) {
|
||||
// Парсим старый формат targetCell (например "A1", "B5")
|
||||
const cellMatch = element.targetCell.match(/^([A-Z]+)(\d+)$/);
|
||||
const cellMatch = element.targetCell.match(/^([A-Z]+)(\d+)$/)
|
||||
if (cellMatch) {
|
||||
targetCells.push({
|
||||
sheet: 'Report', // По умолчанию используем Report
|
||||
cell: element.targetCell,
|
||||
displayName: `Ячейка ${element.targetCell}`,
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +38,7 @@ export function migrateTemplateElement(element: any): TemplateElement {
|
||||
targetCells,
|
||||
order: element.order || 0,
|
||||
layout: element.layout || generateDefaultLayout(element.order || 0),
|
||||
} as TemplateElement;
|
||||
} as TemplateElement
|
||||
}
|
||||
|
||||
// Получение настроек отображения по умолчанию
|
||||
@@ -41,18 +46,18 @@ export function getDefaultLayoutSettings(): FormLayoutSettings {
|
||||
return {
|
||||
gridSize: 20,
|
||||
showGrid: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Генерация позиции по умолчанию для нового элемента
|
||||
export function generateDefaultLayout(order: number = 0): ElementLayout {
|
||||
const cols = 4; // Количество колонок для автоматического размещения
|
||||
const elementWidth = 300;
|
||||
const elementHeight = 80;
|
||||
const padding = 50;
|
||||
const cols = 4 // Количество колонок для автоматического размещения
|
||||
const elementWidth = 300
|
||||
const elementHeight = 80
|
||||
const padding = 50
|
||||
|
||||
const col = order % cols;
|
||||
const row = Math.floor(order / cols);
|
||||
const col = order % cols
|
||||
const row = Math.floor(order / cols)
|
||||
|
||||
return {
|
||||
x: col * (elementWidth + padding) + padding,
|
||||
@@ -60,7 +65,7 @@ export function generateDefaultLayout(order: number = 0): ElementLayout {
|
||||
width: elementWidth,
|
||||
height: elementHeight,
|
||||
zIndex: 1,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Автоматическое размещение элементов на холсте
|
||||
@@ -68,21 +73,21 @@ export function autoArrangeElements(
|
||||
elements: TemplateElement[],
|
||||
gridSize: number = 20
|
||||
): TemplateElement[] {
|
||||
const elementWidth = 300;
|
||||
const elementHeight = 80;
|
||||
const padding = 50;
|
||||
const cols = 4; // Фиксированное количество колонок
|
||||
const elementWidth = 300
|
||||
const elementHeight = 80
|
||||
const padding = 50
|
||||
const cols = 4 // Фиксированное количество колонок
|
||||
|
||||
return elements.map((element, index) => {
|
||||
const col = index % cols;
|
||||
const row = Math.floor(index / cols);
|
||||
const col = index % cols
|
||||
const row = Math.floor(index / cols)
|
||||
|
||||
const x = col * (elementWidth + padding) + padding;
|
||||
const y = row * (elementHeight + padding) + padding;
|
||||
const x = col * (elementWidth + padding) + padding
|
||||
const y = row * (elementHeight + padding) + padding
|
||||
|
||||
// Привязка к сетке
|
||||
const snappedX = Math.round(x / gridSize) * gridSize;
|
||||
const snappedY = Math.round(y / gridSize) * gridSize;
|
||||
const snappedX = Math.round(x / gridSize) * gridSize
|
||||
const snappedY = Math.round(y / gridSize) * gridSize
|
||||
|
||||
return {
|
||||
...element,
|
||||
@@ -93,8 +98,8 @@ export function autoArrangeElements(
|
||||
height: elementHeight,
|
||||
zIndex: 1,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Проверка пересечения элементов
|
||||
@@ -108,7 +113,7 @@ export function checkElementOverlap(
|
||||
element2.x + element2.width + threshold < element1.x ||
|
||||
element1.y + element1.height + threshold < element2.y ||
|
||||
element2.y + element2.height + threshold < element1.y
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
// Поиск свободного места для элемента (поиск в разумных пределах)
|
||||
@@ -117,54 +122,56 @@ export function findFreePosition(
|
||||
existingElements: ElementLayout[],
|
||||
gridSize: number = 20
|
||||
): ElementLayout {
|
||||
const step = gridSize;
|
||||
const maxWidth = 1600; // Максимальная ширина поиска
|
||||
const maxHeight = 1200; // Максимальная высота поиска
|
||||
const maxAttempts = 500;
|
||||
let attempts = 0;
|
||||
const step = gridSize
|
||||
const maxWidth = 1600 // Максимальная ширина поиска
|
||||
const maxHeight = 1200 // Максимальная высота поиска
|
||||
const maxAttempts = 500
|
||||
let attempts = 0
|
||||
|
||||
for (let y = 50; y <= maxHeight - newElement.height; y += step) {
|
||||
for (let x = 50; x <= maxWidth - newElement.width; x += step) {
|
||||
attempts++;
|
||||
if (attempts > maxAttempts) break;
|
||||
attempts++
|
||||
if (attempts > maxAttempts) break
|
||||
|
||||
const testPosition = { ...newElement, x, y };
|
||||
const testPosition = { ...newElement, x, y }
|
||||
const hasOverlap = existingElements.some(existing =>
|
||||
checkElementOverlap(testPosition, existing)
|
||||
);
|
||||
)
|
||||
|
||||
if (!hasOverlap) {
|
||||
return testPosition;
|
||||
return testPosition
|
||||
}
|
||||
}
|
||||
if (attempts > maxAttempts) break;
|
||||
if (attempts > maxAttempts) break
|
||||
}
|
||||
|
||||
// Если не нашли свободное место, размещаем в правом нижнем углу
|
||||
return {
|
||||
...newElement,
|
||||
x: 50,
|
||||
y: existingElements.length * 100 + 50
|
||||
};
|
||||
y: existingElements.length * 100 + 50,
|
||||
}
|
||||
}
|
||||
|
||||
// Валидация адреса ячейки
|
||||
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 {
|
||||
const match = cell.match(/^([A-Z]+)(\d+)$/);
|
||||
if (!match) return null;
|
||||
export function parseCellAddress(
|
||||
cell: string
|
||||
): { column: string; row: number } | null {
|
||||
const match = cell.match(/^([A-Z]+)(\d+)$/)
|
||||
if (!match) return null
|
||||
|
||||
return {
|
||||
column: match[1],
|
||||
row: parseInt(match[2], 10),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Генерация уникального ID для элемента
|
||||
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 { Button } from '@/components/ui/button'
|
||||
import { useTemplateContext } from '@/contexts/TemplateContext'
|
||||
import { Template, TemplateElement } from '@/types/template'
|
||||
import { useTemplateContext } from '@/context/TemplateContext'
|
||||
import { Template, TemplateElement } from '@/type/template'
|
||||
import { ArrowLeft, FileText, Settings, Wrench } from 'lucide-react'
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
@@ -14,10 +14,10 @@ import {
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { useTemplateContext } from '@/contexts/TemplateContext'
|
||||
import { useTemplateContext } from '@/context/TemplateContext'
|
||||
import { getElementDefinition } from '@/lib/element-registry'
|
||||
import { getLatestFileForTemplate } from '@/services/fileApiSevice'
|
||||
import { Template, TemplateElement } from '@/types/template'
|
||||
import { getLatestFileForTemplate } from '@/service/fileApiSevice'
|
||||
import { Template, TemplateElement } from '@/type/template'
|
||||
import {
|
||||
ArrowLeft,
|
||||
Download,
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TemplateManager } from '@/components/TemplateManager/TemplateManager'
|
||||
import { useTemplateContext } from '@/contexts/TemplateContext'
|
||||
import { useTemplateContext } from '@/context/TemplateContext'
|
||||
import { FC, useEffect } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
|
||||
@@ -38,7 +38,7 @@ export interface ApiTemplate {
|
||||
|
||||
// Импортируем типы для преобразования
|
||||
import { getDefaultLayoutSettings, migrateTemplateElement } from '../lib/utils'
|
||||
import { Template, TemplateElement } from '../types/template'
|
||||
import { Template, TemplateElement } from '../type/template'
|
||||
|
||||
// Утилитарные функции для преобразования данных
|
||||
|
||||
@@ -8,12 +8,13 @@
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"strictNullChecks": true,
|
||||
"outDir": "./dist/",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"ts-node": {
|
||||
|
||||
@@ -7,7 +7,7 @@ export default defineConfig({
|
||||
plugins: [react()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve('src'),
|
||||
'@': path.resolve(__dirname, 'src'),
|
||||
'@widgets': path.resolve('src/widgets'),
|
||||
'@features': path.resolve('src/features'),
|
||||
'@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"
|
||||
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@*":
|
||||
version "15.7.15"
|
||||
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"
|
||||
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:
|
||||
version "6.12.6"
|
||||
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"
|
||||
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:
|
||||
version "1.1.3"
|
||||
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"
|
||||
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:
|
||||
version "2.0.1"
|
||||
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"
|
||||
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:
|
||||
version "7.0.6"
|
||||
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"
|
||||
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:
|
||||
version "4.3.7"
|
||||
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"
|
||||
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:
|
||||
version "0.2.0"
|
||||
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"
|
||||
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:
|
||||
version "1.11.1"
|
||||
resolved "https://registry.yarnpkg.com/unrs-resolver/-/unrs-resolver-1.11.1.tgz#be9cd8686c99ef53ecb96df2a473c64d304048a9"
|
||||
@@ -4059,21 +4036,11 @@ which@^2.0.1:
|
||||
dependencies:
|
||||
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:
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
|
||||
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":
|
||||
version "7.0.0"
|
||||
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"
|
||||
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:
|
||||
version "2.8.0"
|
||||
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.0.tgz#15f8c9866211bdc2d3781a0890e44d4fa1a5fff6"
|
||||
|
||||
Reference in New Issue
Block a user