Spaces:
Sleeping
Sleeping
File size: 5,750 Bytes
425a907 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 | import { useState } from 'react';
import { useAppState, useAppDispatch } from '../context/AppContext.jsx';
import Header from '../components/Header.jsx';
import { DEFAULT_CONFIG } from '../utils/constants.js';
import { buildAndDownload } from '../utils/excelExport.js';
import { showInfoToast, showWarnToast } from '../components/Toast.jsx';
function getByPath(obj, path) {
if (!path) return obj;
return path.split('.').reduce((o, k) => (o && o[k] !== undefined ? o[k] : undefined), obj);
}
function setByPath(obj, path, value) {
if (!path) return value;
const keys = path.split('.');
const root = JSON.parse(JSON.stringify(obj));
let cur = root;
for (let i = 0; i < keys.length - 1; i++) { cur = cur[keys[i]]; }
cur[keys[keys.length - 1]] = value;
return root;
}
function ConfigNode({ obj, path, depth }) {
const dispatch = useAppDispatch();
const state = useAppState();
const [collapsed, setCollapsed] = useState(depth > 1);
function update(p, val) {
const newConfig = setByPath(state.config, p, val);
dispatch({ type: 'SET_CONFIG', payload: newConfig });
}
if (Array.isArray(obj)) {
return (
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 6, padding: '2px 0', minHeight: 26 }}>
<input
type="text"
className="cfg-inp cfg-inp-arr"
defaultValue={obj.map(v => JSON.stringify(v)).join(', ')}
title="Edit comma-separated values"
onBlur={e => {
try {
const raw = e.target.value;
const parsed = raw.split(',').map(s => {
const t = s.trim();
try { return JSON.parse(t); } catch { return t; }
}).filter(v => v !== '');
update(path, parsed);
} catch (err) { showWarnToast('Invalid array format'); }
}}
/>
<span className="cfg-type-tag">array[{obj.length}]</span>
</div>
);
}
if (obj !== null && typeof obj === 'object') {
const keys = Object.keys(obj);
return (
<div className="cfg-node">
{depth > 0 && (
<div
className="cfg-obj-label"
style={{ paddingBottom: 2 }}
onClick={() => setCollapsed(!collapsed)}
>
<span style={{ fontSize: 9, color: 'var(--mt)', marginRight: 2, userSelect: 'none' }}>
{collapsed ? '▶' : '▼'}
</span>
<span style={{ color: 'var(--mt)' }}>{'{' + keys.length + ' keys}'}</span>
</div>
)}
{!collapsed && (
<div className={depth > 0 ? 'cfg-children' : ''}>
{keys.map(key => {
const childPath = path ? `${path}.${key}` : key;
const childVal = obj[key];
return (
<div key={key} className="cfg-node">
<div className="cfg-row">
<span className="cfg-key" style={{ marginTop: 4 }}>{key}</span>
<span className="cfg-sep" style={{ marginTop: 4 }}>:</span>
<ConfigNode obj={childVal} path={childPath} depth={depth + 1} />
</div>
</div>
);
})}
</div>
)}
</div>
);
}
// Primitive leaf
const isNum = typeof obj === 'number';
const isBool = typeof obj === 'boolean';
if (isBool) {
return (
<select
className="cfg-inp"
defaultValue={String(obj)}
onChange={e => update(path, e.target.value === 'true')}
style={{ width: 80 }}
>
<option value="true">true</option>
<option value="false">false</option>
</select>
);
}
return (
<input
type={isNum ? 'number' : 'text'}
className={`cfg-inp${isNum ? ' cfg-inp-num' : ''}`}
defaultValue={obj}
onBlur={e => {
const raw = e.target.value;
update(path, isNum ? (isNaN(Number(raw)) ? raw : Number(raw)) : raw);
}}
/>
);
}
export default function Step7Config({ onBack, onNav }) {
const state = useAppState();
const dispatch = useAppDispatch();
const { config } = state;
function resetConfig() {
if (!window.confirm('Reset all config values to defaults?')) return;
dispatch({ type: 'SET_CONFIG', payload: JSON.parse(JSON.stringify(DEFAULT_CONFIG)) });
}
function download() {
const ok = buildAndDownload(state);
if (ok) showInfoToast('Download complete — Dabur_Model_Output.xlsx');
}
return (
<div className="page">
<Header step={7} onNav={onNav} sub="Config Editor" />
<div className="toolbar">
<span style={{ fontSize: 11, fontWeight: 600, color: 'var(--tx)' }}>Model Configuration</span>
<span className="page-note" style={{ marginLeft: 4 }}>Edit values inline · Click ▶ to expand objects</span>
<div style={{ flex: 1 }} />
<button className="btn btn-sm btn-sec" onClick={resetConfig}>Reset to Defaults</button>
</div>
<div style={{ flex: 1, overflow: 'auto', padding: '10px 14px', minHeight: 0 }}>
<ConfigNode obj={config} path="" depth={0} />
</div>
<div className="nav-foot">
<button className="btn btn-sec" onClick={onBack}><svg width="9" height="9" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="2.3" strokeLinecap="round"><path d="M10 6H2M5 2L1 6l4 4"/></svg> Back</button>
<button className="btn btn-or" onClick={download}>
<svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><path d="M8 2v8M5 7l3 3 3-3"/><path d="M2 12v1a1 1 0 001 1h10a1 1 0 001-1v-1"/></svg>
Full Excel Output
</button>
</div>
</div>
);
}
|