SessionZeroWasm/old-react-datasets.txt
2025-07-01 23:23:12 -05:00

320 lines
14 KiB
Plaintext

import React, { useState, useEffect, useRef } from 'react';
import './DatasetManager.css';
import { Dataset, LibraryEntry, DataRecord, DataValue } from '../../types';
import { HugeiconsIcon } from "@hugeicons/react";
import { AddCircleIcon, Delete02Icon, FolderAddIcon, Edit02Icon } from "@hugeicons/core-free-icons";
// Reusable Modal Component
const Modal: React.FC<{
isOpen: boolean;
onClose: () => void;
title: string;
children: React.ReactNode;
actions: { label: string; onClick: () => void; className?: string }[];
size?: 'default' | 'large';
}> = ({ isOpen, onClose, title, children, actions, size = 'default' }) => {
if (!isOpen) return null;
const modalContentClass = `modal-content ${size === 'large' ? 'entry-editor-content' : ''}`;
return (
<div className="modal-overlay" onClick={onClose}>
<div className={modalContentClass} onClick={e => e.stopPropagation()}>
<h3>{title}</h3>
<div className="modal-body">{children}</div>
<div className="modal-actions">
{actions.map((action, index) => (
<button key={index} onClick={action.onClick} className={`builder-btn ${action.className || ''}`}>
{action.label}
</button>
))}
</div>
</div>
</div>
);
};
// Data Record Editor Component
const DataRecordEditor = ({ data, onChange }: { data: DataRecord, onChange: (newData: DataRecord) => void }) => {
const [addingInfo, setAddingInfo] = useState<{type: 'field' | 'group'} | null>(null);
const [newKey, setNewKey] = useState('');
const newKeyInputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
if (addingInfo) {
newKeyInputRef.current?.focus();
}
}, [addingInfo]);
const handleFieldChange = (key: string, value: DataValue) => {
onChange({ ...data, [key]: value });
};
const handleRemoveField = (key: string) => {
const { [key]: _, ...rest } = data;
onChange(rest);
};
const handleInitiateAdd = (type: 'field' | 'group') => {
setAddingInfo({ type });
};
const handleConfirmAdd = () => {
const key = newKey.trim();
if (key && !data.hasOwnProperty(key)) {
onChange({ ...data, [key]: addingInfo?.type === 'group' ? {} : '' });
setNewKey('');
setAddingInfo(null);
} else if (key) {
alert(`The key "${key}" already exists.`);
} else {
setAddingInfo(null);
}
};
return (
<div className="data-record-editor">
{Object.keys(data).length === 0 && <p className="placeholder-text">This entry is empty. Add a field or group to begin.</p>}
{Object.entries(data).map(([key, value]) => (
<div key={key} className="data-field-row">
<label>{key}</label>
<div className="data-field-input-group">
{typeof value === 'object' && value !== null ?
(
<div className="data-group-container">
<DataRecordEditor
data={value as DataRecord}
onChange={(newValue) => handleFieldChange(key, newValue)}
/>
</div>
) :
( <textarea value={value as string || ''} onChange={e => handleFieldChange(key, e.target.value)} rows={3} /> )
}
</div>
<button onClick={() => handleRemoveField(key)} className="builder-btn remove-btn">
<HugeiconsIcon icon={Delete02Icon} size={16}/>
</button>
</div>
))}
{addingInfo ? (
<div className="add-field-form">
<input
ref={newKeyInputRef}
type="text"
value={newKey}
onChange={e => setNewKey(e.target.value)}
onKeyDown={e => e.key === 'Enter' && handleConfirmAdd()}
placeholder={`New ${addingInfo.type} name...`}
/>
<button className="builder-btn save-btn" onClick={handleConfirmAdd}>Save</button>
<button className="builder-btn load-btn" onClick={() => setAddingInfo(null)}>Cancel</button>
</div>
) : (
<div className="data-editor-actions">
<button className="builder-btn add-btn" onClick={() => handleInitiateAdd('field')}><HugeiconsIcon icon={AddCircleIcon} size={14}/> Add Field</button>
<button className="builder-btn add-btn" onClick={() => handleInitiateAdd('group')}><HugeiconsIcon icon={FolderAddIcon} size={14}/> Add Group</button>
</div>
)}
</div>
);
};
// Main Dataset Manager Component
function DatasetManager() {
const [datasets, setDatasets] = useState<Dataset[]>(() => {
try {
const stored = localStorage.getItem('libraryDatasets');
return stored ? JSON.parse(stored) : [];
} catch (error) {
console.error("Failed to parse datasets from localStorage", error);
return [];
}
});
const [selectedDatasetId, setSelectedDatasetId] = useState<number | null>(null);
const [editingEntry, setEditingEntry] = useState<LibraryEntry | null>(null);
const [showCreateDatasetModal, setShowCreateDatasetModal] = useState(false);
const [newDatasetInfo, setNewDatasetInfo] = useState({ name: '', type: 'generic' });
const [confirmDeleteInfo, setConfirmDeleteInfo] = useState<{type: 'dataset'|'entry', id: number, name: string} | null>(null);
// Derive selectedDataset from state - this is our single source of truth
const selectedDataset = datasets.find(d => d.id === selectedDatasetId) || null;
// Effect to save to localStorage whenever datasets change
useEffect(() => {
try {
localStorage.setItem('libraryDatasets', JSON.stringify(datasets));
} catch (error) {
console.error("Failed to save datasets to localStorage", error);
}
}, [datasets]);
const handleSaveNewDataset = () => {
if (!newDatasetInfo.name.trim()) {
alert("Dataset name is required.");
return;
}
const newDataset: Dataset = { id: Date.now(), name: newDatasetInfo.name, type: newDatasetInfo.type || 'generic', entries: [] };
setDatasets([...datasets, newDataset]);
setShowCreateDatasetModal(false);
setNewDatasetInfo({ name: '', type: 'generic' });
};
const handleExecuteDelete = () => {
if (!confirmDeleteInfo) return;
const { type, id } = confirmDeleteInfo;
if (type === 'dataset') {
setDatasets(datasets.filter(d => d.id !== id));
if (selectedDatasetId === id) {
setSelectedDatasetId(null);
setEditingEntry(null);
}
} else if (type === 'entry' && selectedDatasetId) {
const updatedDatasets = datasets.map(d => {
if (d.id === selectedDatasetId) {
return { ...d, entries: d.entries.filter(e => e.id !== id) };
}
return d;
});
setDatasets(updatedDatasets);
}
setConfirmDeleteInfo(null);
};
const handleAddOrUpdateEntry = (entry: LibraryEntry) => {
if (!selectedDatasetId || !entry.name.trim()) return;
const updatedDatasets = datasets.map(d => {
if (d.id === selectedDatasetId) {
const isNew = entry.id === 0;
const updatedEntries = isNew
? [...d.entries, { ...entry, id: Date.now() }]
: d.entries.map(e => e.id === entry.id ? entry : e);
return { ...d, entries: updatedEntries };
}
return d;
});
setDatasets(updatedDatasets);
setEditingEntry(null);
};
return (
<div className="dataset-manager-container">
{/* Entry Editor Modal */}
{editingEntry && (
<Modal
isOpen={!!editingEntry}
onClose={() => setEditingEntry(null)}
title={editingEntry.id === 0 ? 'Create New Entry' : 'Edit Entry'}
size="large"
actions={[
{ label: 'Cancel', onClick: () => setEditingEntry(null), className: 'load-btn' },
{ label: 'Save Entry', onClick: () => handleAddOrUpdateEntry(editingEntry), className: 'save-btn' }
]}
>
<div className="form-group">
<label>Entry Name</label>
<input
type="text"
placeholder="e.g., 'Longsword' or 'Fireball'"
value={editingEntry.name}
onChange={(e) => setEditingEntry({ ...editingEntry, name: e.target.value })}
/>
</div>
<div className="form-group">
<label>Data</label>
<DataRecordEditor
data={editingEntry.data}
onChange={(newData) => setEditingEntry({ ...editingEntry, data: newData })}
/>
</div>
</Modal>
)}
{/* Create Dataset Modal */}
<Modal
isOpen={showCreateDatasetModal}
onClose={() => setShowCreateDatasetModal(false)}
title="Create New Dataset"
actions={[
{ label: 'Cancel', onClick: () => setShowCreateDatasetModal(false), className: 'load-btn' },
{ label: 'Create', onClick: handleSaveNewDataset, className: 'save-btn' }
]}
>
<div className="form-group">
<label htmlFor="datasetName">Dataset Name</label>
<input id="datasetName" type="text" placeholder='e.g., "Core Rulebook Items"' value={newDatasetInfo.name} onChange={e => setNewDatasetInfo({...newDatasetInfo, name: e.target.value})}/>
</div>
<div className="form-group">
<label htmlFor="datasetType">Dataset Type</label>
<input id="datasetType" type="text" placeholder='e.g., "spells" or "equipment"' value={newDatasetInfo.type} onChange={e => setNewDatasetInfo({...newDatasetInfo, type: e.target.value})}/>
</div>
</Modal>
{/* Confirm Deletion Modal */}
<Modal
isOpen={!!confirmDeleteInfo}
onClose={() => setConfirmDeleteInfo(null)}
title="Confirm Deletion"
actions={[
{ label: 'Cancel', onClick: () => setConfirmDeleteInfo(null), className: 'load-btn' },
{ label: 'Delete', onClick: handleExecuteDelete, className: 'delete-btn' }
]}
>
<p>Are you sure you want to delete the {confirmDeleteInfo?.type} "<strong>{confirmDeleteInfo?.name}</strong>"? This action cannot be undone.</p>
</Modal>
<div className="datasets-list-panel">
<div className="panel-header">
<h3>Datasets</h3>
</div>
<button className="builder-btn new-dataset-btn" onClick={() => { setNewDatasetInfo({name: '', type: 'generic'}); setShowCreateDatasetModal(true); }}>
<HugeiconsIcon icon={FolderAddIcon} size={16}/> New Dataset
</button>
<ul className="list-group">
{datasets.map(d => (
<li key={d.id}
className={`list-group-item ${selectedDatasetId === d.id ? 'active' : ''}`}
onClick={() => setSelectedDatasetId(d.id)}>
<div className="dataset-info">
<span className="dataset-name">{d.name}</span>
<span className="dataset-type">{d.type}</span>
</div>
<button className="builder-btn delete-btn" onClick={(e) => { e.stopPropagation(); setConfirmDeleteInfo({type: 'dataset', id: d.id, name: d.name})}}>
<HugeiconsIcon icon={Delete02Icon} size={16}/>
</button>
</li>
))}
</ul>
</div>
<div className="entries-list-panel">
{selectedDataset ? (
<>
<div className="panel-header">
<h3>{selectedDataset.name}</h3>
<button className="builder-btn save-btn" onClick={() => setEditingEntry({id: 0, name: '', data: { 'Description': '' }})}>
<HugeiconsIcon icon={AddCircleIcon} size={16}/> New Entry
</button>
</div>
<ul className="list-group">
{selectedDataset.entries.map(entry => (
<li key={entry.id} className="list-group-item">
<span className="dataset-info">{entry.name}</span>
<div className="entry-actions">
<button className="builder-btn load-btn" onClick={() => setEditingEntry(entry)}><HugeiconsIcon icon={Edit02Icon} size={16}/></button>
<button className="builder-btn delete-btn" onClick={() => setConfirmDeleteInfo({type: 'entry', id: entry.id, name: entry.name})}><HugeiconsIcon icon={Delete02Icon} size={16}/></button>
</div>
</li>
))}
</ul>
</>
) : <div className="placeholder-text">Select a dataset to view its entries.</div>}
</div>
</div>
);
}
export default DatasetManager;