161 lines
7.2 KiB
JavaScript
161 lines
7.2 KiB
JavaScript
import React, { useState } from 'react';
|
|
|
|
const ELEMENT_TYPES = [
|
|
{ value: 'progress_bar', label: 'Progress Bar' },
|
|
{ value: 'gauge', label: 'Gauge' },
|
|
{ value: 'icon', label: 'Icon' },
|
|
{ value: 'text_label', label: 'Text Label' },
|
|
{ value: 'radial', label: 'Radial Dial' },
|
|
{ value: 'waveform', label: 'Waveform' },
|
|
];
|
|
|
|
const COLORS = ['#e74c3c', '#f39c12', '#3498db', '#8e44ad', '#e91e63', '#ff6b6b', '#2ecc71', '#1abc9c'];
|
|
|
|
export default function UIElementEditor({ uiElements, needs, onAdd, onUpdate, onDelete }) {
|
|
const [editingId, setEditingId] = useState(null);
|
|
const [editForm, setEditForm] = useState({});
|
|
|
|
const startEdit = (el) => {
|
|
setEditingId(el.id);
|
|
setEditForm({ ...el, config: typeof el.config === 'string' ? JSON.parse(el.config) : el.config });
|
|
};
|
|
|
|
const cancelEdit = () => {
|
|
setEditingId(null);
|
|
setEditForm({});
|
|
};
|
|
|
|
const saveEdit = () => {
|
|
onUpdate(editingId, editForm);
|
|
setEditingId(null);
|
|
};
|
|
|
|
const handleAdd = () => {
|
|
onAdd({
|
|
need_id: needs?.[0]?.id || null,
|
|
element_type: 'progress_bar',
|
|
config: { color: '#3498db', label: 'New Element', animation: 'smooth' },
|
|
});
|
|
};
|
|
|
|
return (
|
|
<div className="ui-element-editor">
|
|
<div className="editor-toolbar">
|
|
<h3>UI Elements</h3>
|
|
<button className="btn-primary" onClick={handleAdd}>Add UI Element</button>
|
|
</div>
|
|
|
|
{(!uiElements || uiElements.length === 0) ? (
|
|
<p className="empty-state">No UI elements defined. These represent how needs will visually appear.</p>
|
|
) : (
|
|
<div className="ui-element-list">
|
|
{uiElements.map((el) => {
|
|
const config = typeof el.config === 'string' ? JSON.parse(el.config) : el.config;
|
|
const need = needs?.find((n) => n.id === el.need_id);
|
|
|
|
return (
|
|
<div key={el.id} className="ui-element-card">
|
|
{editingId === el.id ? (
|
|
<div className="ui-edit-form">
|
|
<div className="form-row">
|
|
<label>Linked Need</label>
|
|
<select value={editForm.need_id || ''} onChange={(e) => setEditForm({ ...editForm, need_id: e.target.value })}>
|
|
<option value="">None</option>
|
|
{needs?.map((n) => (
|
|
<option key={n.id} value={n.id}>{n.name}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
<div className="form-row">
|
|
<label>Element Type</label>
|
|
<select value={editForm.element_type} onChange={(e) => setEditForm({ ...editForm, element_type: e.target.value })}>
|
|
{ELEMENT_TYPES.map((t) => (
|
|
<option key={t.value} value={t.value}>{t.label}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
<div className="form-row">
|
|
<label>Color</label>
|
|
<div className="color-picker">
|
|
{COLORS.map((c) => (
|
|
<button
|
|
key={c}
|
|
className={`color-swatch ${editForm.config?.color === c ? 'selected' : ''}`}
|
|
style={{ backgroundColor: c }}
|
|
onClick={() => setEditForm({ ...editForm, config: { ...editForm.config, color: c } })}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
<div className="form-row">
|
|
<label>Label</label>
|
|
<input value={editForm.config?.label || ''} onChange={(e) => setEditForm({ ...editForm, config: { ...editForm.config, label: e.target.value } })} />
|
|
</div>
|
|
<div className="form-row">
|
|
<label>Animation</label>
|
|
<select value={editForm.config?.animation || 'none'} onChange={(e) => setEditForm({ ...editForm, config: { ...editForm.config, animation: e.target.value } })}>
|
|
<option value="none">None</option>
|
|
<option value="smooth">Smooth</option>
|
|
<option value="pulse">Pulse</option>
|
|
<option value="bounce">Bounce</option>
|
|
</select>
|
|
</div>
|
|
<div className="form-actions">
|
|
<button className="btn-primary" onClick={saveEdit}>Save</button>
|
|
<button className="btn-secondary" onClick={cancelEdit}>Cancel</button>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<>
|
|
<div className="ui-element-preview" style={{ borderLeftColor: config?.color || '#95a5a6' }}>
|
|
<div className="ui-element-header">
|
|
<h4>{config?.label || el.element_type}</h4>
|
|
<span className="element-type-badge">{ELEMENT_TYPES.find(t => t.value === el.element_type)?.label || el.element_type}</span>
|
|
</div>
|
|
<div className="ui-element-details">
|
|
<span>Linked to: <strong>{need?.name || 'None'}</strong></span>
|
|
<span>Animation: <strong>{config?.animation || 'none'}</strong></span>
|
|
</div>
|
|
<div className="ui-element-visual">
|
|
{el.element_type === 'progress_bar' && (
|
|
<div className="visual-progress-bar">
|
|
<div className="visual-progress-fill" style={{ width: '60%', backgroundColor: config?.color }} />
|
|
</div>
|
|
)}
|
|
{el.element_type === 'gauge' && (
|
|
<div className="visual-gauge" style={{ color: config?.color }}>
|
|
<span className="gauge-value">75%</span>
|
|
</div>
|
|
)}
|
|
{el.element_type === 'radial' && (
|
|
<div className="visual-radial" style={{ borderColor: config?.color }}>
|
|
<span>60%</span>
|
|
</div>
|
|
)}
|
|
{(el.element_type === 'icon' || el.element_type === 'text_label') && (
|
|
<div className="visual-icon" style={{ color: config?.color }}>
|
|
{config?.label || el.element_type}
|
|
</div>
|
|
)}
|
|
{el.element_type === 'waveform' && (
|
|
<div className="visual-waveform" style={{ color: config?.color }}>
|
|
~ ~ ~
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className="ui-element-actions">
|
|
<button className="btn-small" onClick={() => startEdit(el)}>Edit</button>
|
|
<button className="btn-danger-small" onClick={() => onDelete(el.id)}>Delete</button>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|