Fix config view

This commit is contained in:
Халимов Рустам
2026-01-12 00:13:32 +03:00
parent e7d153ce6b
commit 58a2e0468f

View File

@@ -1,6 +1,6 @@
import React, { useEffect, useRef } from 'react';
import React, { useEffect } from 'react';
import { AppConfig, PerforationPattern } from '../types';
import { Settings2, Grid, Circle, Triangle, Hexagon, LayoutGrid } from 'lucide-react';
import { Settings2, Box, Ruler, LayoutGrid, Circle, Hexagon, Triangle, Scan } from 'lucide-react';
interface Props {
config: AppConfig;
@@ -9,17 +9,17 @@ interface Props {
export const ConfigStep: React.FC<Props> = ({ config, onChange }) => {
// Инициализация дефолтных значений, если их нет
// Инициализация дефолтных значений перфорации
useEffect(() => {
if (!config.perforation) {
onChange({
...config,
perforation: { enabled: false, pattern: 'hexagon', diameter: 8, spacing: 4 }
perforation: { enabled: false, pattern: 'hexagon', diameter: 8, spacing: 2 }
});
}
}, []);
const perf = config.perforation || { enabled: false, pattern: 'hexagon', diameter: 8, spacing: 4 };
const perf = config.perforation || { enabled: false, pattern: 'hexagon', diameter: 8, spacing: 2 };
const updatePerf = (updates: Partial<typeof perf>) => {
onChange({ ...config, perforation: { ...perf, ...updates } });
@@ -29,41 +29,46 @@ export const ConfigStep: React.FC<Props> = ({ config, onChange }) => {
onChange({ ...config, drawer: { ...config.drawer, [key]: value } });
};
// --- RENDER PREVIEW (SVG) ---
// --- RENDER PREVIEW ---
const renderPreview = () => {
if (!perf.enabled) return <div className="text-gray-500 text-xs flex items-center justify-center h-full">Перфорация выключена</div>;
const size = perf.diameter;
const gap = Math.max(2, perf.spacing); // Минимальный зазор 2мм
const gap = Math.max(2, perf.spacing);
const step = size + gap;
const W = 140;
const H = 100;
const W = 200;
const H = 120;
const elements = [];
const rows = Math.floor(H / (step * 0.866)); // 0.866 для сот (sin 60)
// Приблизительный расчет для превью
const rows = Math.floor(H / (step * 0.866));
const cols = Math.floor(W / step);
const startX = (W - (cols * step)) / 2;
const startY = (H - (rows * step * 0.866)) / 2;
for(let j=0; j<rows; j++) {
const isOdd = j % 2 !== 0;
const y = 10 + j * (perf.pattern === 'circle' ? step : step * 0.866);
const y = startY + j * (perf.pattern === 'circle' ? step : step * 0.866);
for(let i=0; i<cols; i++) {
let x = 10 + i * step;
let x = startX + i * step;
if ((perf.pattern === 'hexagon' || perf.pattern === 'triangle') && isOdd) x += step / 2;
if (x > W - 10 || y > H - 10) continue;
if (x > W - size || y > H - size) continue;
const color = "#3b82f6";
if (perf.pattern === 'circle') {
elements.push(<circle key={`${i}-${j}`} cx={x} cy={y} r={size/2} fill="#3b82f6" />);
elements.push(<circle key={`${i}-${j}`} cx={x} cy={y} r={size/2} fill={color} />);
} else if (perf.pattern === 'hexagon') {
// Рисуем шестиугольник
const r = size / 2;
const points = [];
for (let k = 0; k < 6; k++) {
const angle = (k * 60 + 30) * Math.PI / 180;
points.push(`${x + r * Math.cos(angle)},${y + r * Math.sin(angle)}`);
}
elements.push(<polygon key={`${i}-${j}`} points={points.join(' ')} fill="#3b82f6" />);
elements.push(<polygon key={`${i}-${j}`} points={points.join(' ')} fill={color} />);
} else if (perf.pattern === 'triangle') {
const r = size / 2;
const angleOffset = isOdd ? 180 : 0;
@@ -72,100 +77,208 @@ export const ConfigStep: React.FC<Props> = ({ config, onChange }) => {
const angle = (k * 120 - 90 + angleOffset) * Math.PI / 180;
points.push(`${x + r * Math.cos(angle)},${y + r * Math.sin(angle)}`);
}
elements.push(<polygon key={`${i}-${j}`} points={points.join(' ')} fill="#3b82f6" />);
elements.push(<polygon key={`${i}-${j}`} points={points.join(' ')} fill={color} />);
}
}
}
return (
<svg viewBox={`0 0 ${W} ${H}`} className="w-full h-full bg-slate-900 border border-slate-700 rounded">
<svg viewBox={`0 0 ${W} ${H}`} className="w-full h-full bg-slate-950/50 border border-slate-700/50 rounded">
{elements}
</svg>
);
};
return (
<div className="space-y-6 pb-20">
{/* 1. ГАБАРИТЫ */}
<section className="bg-slate-900 p-5 rounded-xl border border-slate-800 shadow-lg">
<h2 className="text-lg font-bold mb-4 flex items-center gap-2 text-primary"><Settings2 size={20}/> 1. Размеры ящика</h2>
<div className="grid grid-cols-3 gap-4">
<div>
<label className="text-xs text-gray-400 font-bold ml-1">Ширина (X)</label>
<input type="number" value={config.drawer.width} onChange={(e) => updateDrawer('width', parseFloat(e.target.value))} className="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white focus:ring-2 focus:ring-primary outline-none mt-1"/>
</div>
<div>
<label className="text-xs text-gray-400 font-bold ml-1">Глубина (Y)</label>
<input type="number" value={config.drawer.depth} onChange={(e) => updateDrawer('depth', parseFloat(e.target.value))} className="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white focus:ring-2 focus:ring-primary outline-none mt-1"/>
</div>
<div>
<label className="text-xs text-gray-400 font-bold ml-1">Высота (Z)</label>
<input type="number" value={config.drawer.height} onChange={(e) => updateDrawer('height', parseFloat(e.target.value))} className="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white focus:ring-2 focus:ring-primary outline-none mt-1"/>
</div>
</div>
</section>
<div className="max-w-5xl mx-auto space-y-6 pb-24">
{/* ВЕРХНИЙ БЛОК: Размеры и Параметры печати */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* 1. РАЗМЕРЫ */}
<section className="bg-slate-900 p-6 rounded-xl border border-slate-800 shadow-xl">
<h2 className="text-lg font-bold mb-6 flex items-center gap-2 text-blue-400">
<Box size={20}/> 1. Размеры
</h2>
<div className="space-y-5">
<div className="bg-slate-950/50 p-4 rounded-lg border border-slate-800/50">
<h3 className="text-xs font-bold text-gray-400 uppercase mb-3 flex items-center gap-2"><Ruler size={14}/> Внутренние размеры ящика</h3>
<div className="space-y-4">
<div>
<label className="text-xs text-gray-500 mb-1.5 block ml-1">Ширина (X)</label>
<div className="relative">
<input type="number" value={config.drawer.width} onChange={(e) => updateDrawer('width', parseFloat(e.target.value))} className="w-full bg-slate-800 border border-slate-700 rounded-lg pl-4 pr-12 py-2.5 text-white focus:ring-2 focus:ring-blue-500/50 outline-none transition-all font-mono"/>
<span className="absolute right-4 top-1/2 -translate-y-1/2 text-xs text-gray-500 font-bold">MM</span>
</div>
</div>
<div>
<label className="text-xs text-gray-500 mb-1.5 block ml-1">Глубина (Y)</label>
<div className="relative">
<input type="number" value={config.drawer.depth} onChange={(e) => updateDrawer('depth', parseFloat(e.target.value))} className="w-full bg-slate-800 border border-slate-700 rounded-lg pl-4 pr-12 py-2.5 text-white focus:ring-2 focus:ring-blue-500/50 outline-none transition-all font-mono"/>
<span className="absolute right-4 top-1/2 -translate-y-1/2 text-xs text-gray-500 font-bold">MM</span>
</div>
</div>
<div>
<label className="text-xs text-gray-500 mb-1.5 block ml-1">Высота (Z)</label>
<div className="relative">
<input type="number" value={config.drawer.height} onChange={(e) => updateDrawer('height', parseFloat(e.target.value))} className="w-full bg-slate-800 border border-slate-700 rounded-lg pl-4 pr-12 py-2.5 text-white focus:ring-2 focus:ring-blue-500/50 outline-none transition-all font-mono"/>
<span className="absolute right-4 top-1/2 -translate-y-1/2 text-xs text-gray-500 font-bold">MM</span>
</div>
</div>
</div>
</div>
</div>
</section>
{/* 2. ПЕРФОРАЦИЯ */}
<section className="bg-slate-900 p-5 rounded-xl border border-slate-800 shadow-lg">
<div className="flex justify-between items-center mb-4">
<h2 className="text-lg font-bold flex items-center gap-2 text-primary"><LayoutGrid size={20}/> 2. Перфорация (узоры)</h2>
<div className="flex items-center gap-2">
<span className="text-sm text-gray-400">{perf.enabled ? 'Включено' : 'Выключено'}</span>
{/* 2. ПАРАМЕТРЫ ПЕЧАТИ */}
<section className="bg-slate-900 p-6 rounded-xl border border-slate-800 shadow-xl">
<h2 className="text-lg font-bold mb-6 flex items-center gap-2 text-yellow-500">
<Settings2 size={20}/> Параметры печати
</h2>
<div className="bg-slate-950/50 p-4 rounded-lg border border-slate-800/50 space-y-6">
{/* Wall Thickness */}
<div>
<div className="flex justify-between text-xs font-medium text-gray-400 mb-2">
<span>Толщина стенок</span>
<span className="text-blue-400 bg-blue-400/10 px-2 py-0.5 rounded">{config.wallThickness} мм</span>
</div>
<input type="range" min="0.8" max="4.0" step="0.4"
value={config.wallThickness}
onChange={(e) => onChange({...config, wallThickness: parseFloat(e.target.value)})}
className="w-full h-1.5 bg-slate-700 rounded-lg appearance-none cursor-pointer accent-blue-500"
/>
<div className="flex justify-between text-[10px] text-gray-600 mt-1 px-1">
<span>0.8</span><span>4.0</span>
</div>
</div>
{/* Corner Radius */}
<div>
<div className="flex justify-between text-xs font-medium text-gray-400 mb-2">
<span>Радиус скругления</span>
<span className="text-purple-400 bg-purple-400/10 px-2 py-0.5 rounded">{config.cornerRadius} мм</span>
</div>
<input type="range" min="0" max="20" step="1"
value={config.cornerRadius}
onChange={(e) => onChange({...config, cornerRadius: parseFloat(e.target.value)})}
className="w-full h-1.5 bg-slate-700 rounded-lg appearance-none cursor-pointer accent-purple-500"
/>
<div className="flex justify-between text-[10px] text-gray-600 mt-1 px-1">
<span>0</span><span>20</span>
</div>
</div>
{/* Tolerance */}
<div>
<div className="flex justify-between text-xs font-medium text-gray-400 mb-2">
<span>Зазор (Tolerance)</span>
<span className="text-yellow-400 bg-yellow-400/10 px-2 py-0.5 rounded">{config.printerTolerance} мм</span>
</div>
<input type="range" min="0" max="1.0" step="0.05"
value={config.printerTolerance}
onChange={(e) => onChange({...config, printerTolerance: parseFloat(e.target.value)})}
className="w-full h-1.5 bg-slate-700 rounded-lg appearance-none cursor-pointer accent-yellow-500"
/>
<div className="flex justify-between text-[10px] text-gray-600 mt-1 px-1">
<span>0.0</span><span>1.0</span>
</div>
</div>
</div>
</section>
</div>
{/* НИЖНИЙ БЛОК: ПЕРФОРАЦИЯ */}
<section className={`bg-slate-900 p-6 rounded-xl border border-slate-800 shadow-xl transition-all ${perf.enabled ? 'border-blue-500/30' : ''}`}>
<div className="flex justify-between items-center mb-6">
<h2 className={`text-lg font-bold flex items-center gap-2 ${perf.enabled ? 'text-blue-400' : 'text-gray-500'}`}>
<LayoutGrid size={20}/> 2. Перфорация (узоры)
</h2>
<div className="flex items-center gap-3">
<span className="text-xs font-bold text-gray-500 uppercase tracking-wider">{perf.enabled ? 'Включено' : 'Выключено'}</span>
<button
onClick={() => updatePerf({ enabled: !perf.enabled })}
className={`w-12 h-6 rounded-full p-1 transition-colors ${perf.enabled ? 'bg-primary' : 'bg-slate-700'}`}
className={`w-14 h-7 rounded-full p-1 transition-all duration-300 ${perf.enabled ? 'bg-blue-600 shadow-lg shadow-blue-900/50' : 'bg-slate-700'}`}
>
<div className={`w-4 h-4 bg-white rounded-full shadow-md transform transition-transform ${perf.enabled ? 'translate-x-6' : 'translate-x-0'}`}/>
<div className={`w-5 h-5 bg-white rounded-full shadow-sm transform transition-transform duration-300 ${perf.enabled ? 'translate-x-7' : 'translate-x-0'}`}/>
</button>
</div>
</div>
<div className={`grid grid-cols-1 md:grid-cols-2 gap-6 transition-opacity ${perf.enabled ? 'opacity-100' : 'opacity-50 pointer-events-none'}`}>
{/* Controls */}
<div className="space-y-4">
<div className={`grid grid-cols-1 md:grid-cols-2 gap-8 transition-all duration-500 ${perf.enabled ? 'opacity-100 max-h-[500px]' : 'opacity-40 pointer-events-none grayscale max-h-[100px] overflow-hidden'}`}>
{/* Настройки */}
<div className="space-y-6">
{/* Тип узора */}
<div>
<label className="text-xs text-gray-400 font-bold ml-1 mb-2 block">Тип узора</label>
<div className="flex gap-2">
<button onClick={() => updatePerf({ pattern: 'circle' })} className={`flex-1 py-2 rounded border flex items-center justify-center gap-2 ${perf.pattern === 'circle' ? 'bg-primary/20 border-primary text-primary' : 'bg-slate-800 border-slate-700 text-gray-400'}`}><Circle size={16}/> Круг</button>
<button onClick={() => updatePerf({ pattern: 'hexagon' })} className={`flex-1 py-2 rounded border flex items-center justify-center gap-2 ${perf.pattern === 'hexagon' ? 'bg-primary/20 border-primary text-primary' : 'bg-slate-800 border-slate-700 text-gray-400'}`}><Hexagon size={16}/> Соты</button>
<button onClick={() => updatePerf({ pattern: 'triangle' })} className={`flex-1 py-2 rounded border flex items-center justify-center gap-2 ${perf.pattern === 'triangle' ? 'bg-primary/20 border-primary text-primary' : 'bg-slate-800 border-slate-700 text-gray-400'}`}><Triangle size={16}/> Треуг.</button>
<label className="text-xs font-bold text-gray-400 uppercase mb-3 block">Тип узора</label>
<div className="grid grid-cols-3 gap-3">
<button onClick={() => updatePerf({ pattern: 'circle' })} className={`py-3 rounded-lg border flex flex-col items-center justify-center gap-2 transition-all ${perf.pattern === 'circle' ? 'bg-blue-500/10 border-blue-500 text-blue-400 shadow-lg shadow-blue-900/20' : 'bg-slate-800 border-slate-700 text-gray-500 hover:bg-slate-700 hover:text-gray-300'}`}>
<Circle size={20}/> <span className="text-xs font-medium">Круг</span>
</button>
<button onClick={() => updatePerf({ pattern: 'hexagon' })} className={`py-3 rounded-lg border flex flex-col items-center justify-center gap-2 transition-all ${perf.pattern === 'hexagon' ? 'bg-blue-500/10 border-blue-500 text-blue-400 shadow-lg shadow-blue-900/20' : 'bg-slate-800 border-slate-700 text-gray-500 hover:bg-slate-700 hover:text-gray-300'}`}>
<Hexagon size={20}/> <span className="text-xs font-medium">Соты</span>
</button>
<button onClick={() => updatePerf({ pattern: 'triangle' })} className={`py-3 rounded-lg border flex flex-col items-center justify-center gap-2 transition-all ${perf.pattern === 'triangle' ? 'bg-blue-500/10 border-blue-500 text-blue-400 shadow-lg shadow-blue-900/20' : 'bg-slate-800 border-slate-700 text-gray-500 hover:bg-slate-700 hover:text-gray-300'}`}>
<Triangle size={20}/> <span className="text-xs font-medium">Треуг.</span>
</button>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
{/* Ползунки параметров */}
<div className="bg-slate-950/50 p-4 rounded-lg border border-slate-800/50 space-y-6">
{/* Diameter */}
<div>
<label className="text-xs text-gray-400 font-bold ml-1">Диаметр (мм)</label>
<input type="number" min="2" max="12" step="0.5" value={perf.diameter} onChange={(e) => updatePerf({ diameter: Math.min(12, parseFloat(e.target.value)) })} className="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white mt-1"/>
<div className="flex justify-between text-xs font-medium text-gray-400 mb-2">
<span>Диаметр отверстий</span>
<span className="text-blue-400 bg-blue-400/10 px-2 py-0.5 rounded font-mono">{perf.diameter} мм</span>
</div>
<input type="range" min="2" max="12" step="0.5"
value={perf.diameter}
onChange={(e) => updatePerf({ diameter: parseFloat(e.target.value) })}
className="w-full h-1.5 bg-slate-700 rounded-lg appearance-none cursor-pointer accent-blue-500"
/>
<div className="flex justify-between text-[10px] text-gray-600 mt-1 px-1">
<span>2 мм</span><span>12 мм</span>
</div>
</div>
{/* Spacing */}
<div>
<label className="text-xs text-gray-400 font-bold ml-1">Зазор (мм)</label>
<input type="number" min="2" value={perf.spacing} onChange={(e) => updatePerf({ spacing: Math.max(2, parseFloat(e.target.value)) })} className="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white mt-1"/>
<div className="flex justify-between text-xs font-medium text-gray-400 mb-2">
<span>Зазор (между отверстиями)</span>
<span className="text-blue-400 bg-blue-400/10 px-2 py-0.5 rounded font-mono">{perf.spacing} мм</span>
</div>
<input type="range" min="2" max="10" step="0.5"
value={perf.spacing}
onChange={(e) => updatePerf({ spacing: parseFloat(e.target.value) })}
className="w-full h-1.5 bg-slate-700 rounded-lg appearance-none cursor-pointer accent-blue-500"
/>
<div className="flex justify-between text-[10px] text-gray-600 mt-1 px-1">
<span>2 мм</span><span>10 мм</span>
</div>
</div>
</div>
</div>
{/* Preview */}
<div className="h-32 md:h-auto flex flex-col">
<span className="text-xs text-gray-400 mb-2 font-bold ml-1">Предпросмотр</span>
{renderPreview()}
<div className="flex flex-col">
<div className="flex justify-between items-end mb-2">
<label className="text-xs font-bold text-gray-400 uppercase">Предпросмотр</label>
<span className="text-[10px] text-gray-600 flex items-center gap-1"><Scan size={10}/> Масштаб условен</span>
</div>
<div className="flex-1 min-h-[200px] bg-slate-950 rounded-lg border border-slate-800 relative overflow-hidden flex items-center justify-center p-4">
<div className="w-full h-full max-w-[300px] max-h-[180px] shadow-2xl">
{renderPreview()}
</div>
</div>
</div>
</div>
</section>
{/* 3. ПРОЧЕЕ */}
<section className="bg-slate-900 p-5 rounded-xl border border-slate-800 shadow-lg opacity-80 hover:opacity-100 transition-opacity">
<h2 className="text-sm font-bold mb-4 text-gray-400">Дополнительно</h2>
<div className="flex gap-4">
<div>
<label className="text-xs text-gray-500 block">Толщина стенок</label>
<input type="number" step="0.1" value={config.wallThickness} onChange={(e) => onChange({...config, wallThickness: parseFloat(e.target.value)})} className="bg-slate-800 border border-slate-700 rounded px-2 py-1 text-sm w-20 text-gray-300"/>
</div>
<div>
<label className="text-xs text-gray-500 block">Скругление углов</label>
<input type="number" value={config.cornerRadius} onChange={(e) => onChange({...config, cornerRadius: parseFloat(e.target.value)})} className="bg-slate-800 border border-slate-700 rounded px-2 py-1 text-sm w-20 text-gray-300"/>
</div>
</div>
</section>
</div>
);
};