[TSM.ID].[11031972] PXE : Platform X Ecosystem I [118 Module -LIVE-]

This commit is contained in:
TSM.ID
2026-05-25 03:50:05 +07:00
commit e820143b3c
673 changed files with 101320 additions and 0 deletions
@@ -0,0 +1,246 @@
// [TSM.ID].[11031972] -- All Rights Reserved. Proprietary & Confidential.
// Module Manager — Supreme Admin CRUD for XCU Module Registry
// Dynamic: Add, Deactivate, Search modules. Data from xcu_iam PostgreSQL.
import { useState } from 'react';
import { useModuleRegistry } from '../context/ModuleRegistryContext';
import DangerModal from './DangerModal';
export default function ModuleManager() {
const { registry, allModules, totalModules, loading, source, addModule, deleteModule, refreshRegistry } = useModuleRegistry();
const [showAddForm, setShowAddForm] = useState(false);
const [searchQuery, setSearchQuery] = useState('');
const [deleteTarget, setDeleteTarget] = useState(null);
// Add module form state
const [newId, setNewId] = useState('');
const [newName, setNewName] = useState('');
const [newGroupId, setNewGroupId] = useState('group1');
const [newGroupName, setNewGroupName] = useState('');
const [newSortOrder, setNewSortOrder] = useState(100);
const [isSubmitting, setIsSubmitting] = useState(false);
// Derive available groups
const groups = Object.entries(registry).map(([key, val]) => ({
id: key,
name: val.name
}));
const handleAddModule = async () => {
if (!newId || !newName) return;
setIsSubmitting(true);
const groupName = newGroupName || (groups.find(g => g.id === newGroupId)?.name || newGroupId);
const success = await addModule({
id: newId,
name: newName,
group_id: newGroupId,
group_name: groupName,
sort_order: Number(newSortOrder)
});
if (success) {
setNewId('');
setNewName('');
setNewSortOrder(totalModules + 1);
setNewGroupName('');
setShowAddForm(false);
}
setIsSubmitting(false);
};
const handleDeleteConfirm = async () => {
if (!deleteTarget) return;
await deleteModule(deleteTarget);
setDeleteTarget(null);
};
// Filter modules
const q = searchQuery.toLowerCase();
const filteredGroups = Object.entries(registry).map(([key, group]) => ({
key,
name: group.name,
modules: (group.modules || []).filter(m =>
!q || m.id.toLowerCase().includes(q) || m.name.toLowerCase().includes(q)
)
})).filter(g => g.modules.length > 0);
const filteredCount = filteredGroups.reduce((sum, g) => sum + g.modules.length, 0);
if (loading) {
return (
<div style={{display: 'flex', alignItems: 'center', justifyContent: 'center', height: '300px', color: 'var(--accent-cyan)', fontFamily: 'monospace'}}>
LOADING SOVEREIGN MODULE REGISTRY...
</div>
);
}
return (
<div style={{animation: 'fadeIn 0.5s ease-out'}}>
<DangerModal
isOpen={!!deleteTarget}
title="DEACTIVATE MODULE"
message={`Anda akan menonaktifkan modul ${deleteTarget}. Modul tidak dihapus permanen, tapi tidak akan muncul di registry aktif. Lanjut?`}
onConfirm={handleDeleteConfirm}
onCancel={() => setDeleteTarget(null)}
confirmText="DEACTIVATE MODULE"
/>
{/* Header */}
<div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '30px', gap: '20px', flexWrap: 'wrap'}}>
<div>
<h2 style={{color: 'var(--text-main)', fontFamily: 'monospace', letterSpacing: '2px', marginBottom: '10px'}}>
SOVEREIGN MODULE REGISTRY
</h2>
<p style={{color: 'var(--text-muted)', fontSize: '0.9rem'}}>
Dynamic Module CRUD Source: <span style={{color: source === 'API' ? 'var(--accent-green)' : 'var(--accent-yellow)', fontWeight: 'bold'}}>{source === 'API' ? 'xcu_iam PostgreSQL' : 'Static Fallback'}</span>
</p>
</div>
<div style={{display: 'flex', gap: '10px', alignItems: 'center'}}>
{/* Stats */}
<div style={{
background: 'rgba(0, 243, 255, 0.1)', border: '1px solid var(--accent-cyan)',
padding: '10px 20px', borderRadius: '8px', fontFamily: 'monospace', textAlign: 'center'
}}>
<div style={{color: 'var(--accent-cyan)', fontSize: '1.8rem', fontWeight: 'bold'}}>{totalModules}</div>
<div style={{color: 'var(--text-muted)', fontSize: '0.7rem', letterSpacing: '1px'}}>ACTIVE MODULES</div>
</div>
<div style={{
background: 'rgba(168, 85, 247, 0.1)', border: '1px solid var(--accent-purple)',
padding: '10px 20px', borderRadius: '8px', fontFamily: 'monospace', textAlign: 'center'
}}>
<div style={{color: 'var(--accent-purple)', fontSize: '1.8rem', fontWeight: 'bold'}}>{Object.keys(registry).length}</div>
<div style={{color: 'var(--text-muted)', fontSize: '0.7rem', letterSpacing: '1px'}}>GROUPS</div>
</div>
<button onClick={refreshRegistry} style={{
padding: '10px 20px', background: 'transparent', border: '1px solid var(--accent-green)',
color: 'var(--accent-green)', borderRadius: '8px', cursor: 'pointer', fontFamily: 'monospace'
}}> REFRESH</button>
<button onClick={() => { setShowAddForm(!showAddForm); setNewSortOrder(totalModules + 1); setNewId(`m${totalModules + 1}`); }} style={{
padding: '10px 20px', background: showAddForm ? 'var(--accent-cyan)' : 'transparent',
border: '1px solid var(--accent-cyan)', borderRadius: '8px', cursor: 'pointer', fontFamily: 'monospace',
color: showAddForm ? '#000' : 'var(--accent-cyan)', fontWeight: 'bold'
}}>+ ADD MODULE</button>
</div>
</div>
{/* Add Module Form */}
{showAddForm && (
<div className="glass-panel" style={{
padding: '25px', marginBottom: '30px', borderLeft: '4px solid var(--accent-cyan)',
animation: 'fadeIn 0.3s ease-out'
}}>
<h3 style={{color: 'var(--accent-cyan)', marginBottom: '20px', fontFamily: 'monospace'}}>FORGE NEW MODULE</h3>
<div style={{display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: '15px'}}>
<div>
<label style={{color: 'var(--text-muted)', fontSize: '0.75rem', display: 'block', marginBottom: '5px'}}>MODULE ID</label>
<input value={newId} onChange={e => setNewId(e.target.value)} placeholder="m100"
style={{width: '100%', padding: '10px', background: 'var(--hover-bg)', border: '1px solid var(--table-border)', color: 'var(--text-main)', borderRadius: '6px', fontFamily: 'monospace'}} />
</div>
<div>
<label style={{color: 'var(--text-muted)', fontSize: '0.75rem', display: 'block', marginBottom: '5px'}}>MODULE NAME</label>
<input value={newName} onChange={e => setNewName(e.target.value)} placeholder="Quantum Flux Capacitor"
style={{width: '100%', padding: '10px', background: 'var(--hover-bg)', border: '1px solid var(--table-border)', color: 'var(--text-main)', borderRadius: '6px', fontFamily: 'monospace'}} />
</div>
<div>
<label style={{color: 'var(--text-muted)', fontSize: '0.75rem', display: 'block', marginBottom: '5px'}}>GROUP</label>
<select value={newGroupId} onChange={e => setNewGroupId(e.target.value)}
style={{width: '100%', padding: '10px', background: 'var(--hover-bg)', border: '1px solid var(--table-border)', color: 'var(--text-main)', borderRadius: '6px', fontFamily: 'monospace'}}>
{groups.map(g => <option key={g.id} value={g.id}>{g.name}</option>)}
<option value="new">+ NEW GROUP</option>
</select>
</div>
{newGroupId === 'new' && (
<div>
<label style={{color: 'var(--text-muted)', fontSize: '0.75rem', display: 'block', marginBottom: '5px'}}>NEW GROUP NAME</label>
<input value={newGroupName} onChange={e => setNewGroupName(e.target.value)} placeholder="KELOMPOK IX: ..."
style={{width: '100%', padding: '10px', background: 'var(--hover-bg)', border: '1px solid var(--table-border)', color: 'var(--text-main)', borderRadius: '6px', fontFamily: 'monospace'}} />
</div>
)}
<div>
<label style={{color: 'var(--text-muted)', fontSize: '0.75rem', display: 'block', marginBottom: '5px'}}>SORT ORDER</label>
<input type="number" value={newSortOrder} onChange={e => setNewSortOrder(e.target.value)}
style={{width: '100%', padding: '10px', background: 'var(--hover-bg)', border: '1px solid var(--table-border)', color: 'var(--text-main)', borderRadius: '6px', fontFamily: 'monospace'}} />
</div>
</div>
<div style={{marginTop: '20px', display: 'flex', gap: '10px'}}>
<button onClick={handleAddModule} disabled={isSubmitting || !newId || !newName} style={{
padding: '10px 30px', background: 'var(--accent-cyan)', color: '#000', border: 'none',
borderRadius: '6px', cursor: 'pointer', fontFamily: 'monospace', fontWeight: 'bold',
opacity: (!newId || !newName || isSubmitting) ? 0.5 : 1
}}>{isSubmitting ? 'FORGING...' : 'FORGE MODULE'}</button>
<button onClick={() => setShowAddForm(false)} style={{
padding: '10px 30px', background: 'transparent', color: 'var(--text-muted)',
border: '1px solid var(--table-border)', borderRadius: '6px', cursor: 'pointer', fontFamily: 'monospace'
}}>CANCEL</button>
</div>
</div>
)}
{/* Search */}
<div style={{marginBottom: '20px', position: 'relative'}}>
<input
value={searchQuery} onChange={e => setSearchQuery(e.target.value)}
placeholder="Search modules by ID or name..."
style={{
width: '100%', padding: '12px 20px', background: 'var(--hover-bg)',
border: '1px solid var(--table-border)', color: 'var(--text-main)',
borderRadius: '8px', fontFamily: 'monospace', fontSize: '0.9rem', outline: 'none'
}}
/>
{searchQuery && (
<span style={{position: 'absolute', right: '15px', top: '12px', color: 'var(--text-muted)', fontFamily: 'monospace', fontSize: '0.8rem'}}>
{filteredCount} / {totalModules}
</span>
)}
</div>
{/* Module Grid */}
<div style={{display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(450px, 1fr))', gap: '20px'}}>
{filteredGroups.map(group => {
const groupColors = {
group1: '#00f3ff', group2: '#ffea00', group3: '#ff003c', group4: '#a855f7',
group5: '#10b981', group6: '#3b82f6', group7: '#f97316', group8: '#ef4444'
};
const color = groupColors[group.key] || '#00f3ff';
return (
<div key={group.key} className="glass-panel" style={{
padding: '20px', borderTop: `3px solid ${color}`,
background: 'var(--panel-bg)', borderRadius: '10px'
}}>
<h3 style={{color, fontSize: '0.85rem', marginBottom: '15px', fontFamily: 'monospace', letterSpacing: '1px',
borderBottom: '1px solid var(--table-border)', paddingBottom: '10px', display: 'flex', justifyContent: 'space-between'}}>
<span>{group.name}</span>
<span style={{color: 'var(--text-muted)'}}>[{group.modules.length}]</span>
</h3>
<div style={{display: 'flex', flexDirection: 'column', gap: '6px', maxHeight: '400px', overflowY: 'auto', paddingRight: '5px'}} className="custom-scrollbar">
{group.modules.map(mod => (
<div key={mod.id} style={{
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
padding: '8px 12px', background: 'var(--hover-bg)', borderRadius: '6px',
border: '1px solid var(--table-border)', transition: 'all 0.2s'
}}>
<div>
<span style={{color: 'var(--text-main)', fontSize: '0.85rem'}}>{mod.name}</span>
<span style={{color: 'var(--text-muted)', fontSize: '0.7rem', marginLeft: '8px', fontFamily: 'monospace'}}>{mod.id}</span>
</div>
<button onClick={() => setDeleteTarget(mod.id)} title="Deactivate module" style={{
background: 'transparent', border: '1px solid rgba(255,0,60,0.3)', color: 'var(--accent-red)',
padding: '4px 10px', borderRadius: '4px', cursor: 'pointer', fontFamily: 'monospace', fontSize: '0.7rem',
transition: 'all 0.2s'
}}></button>
</div>
))}
</div>
</div>
);
})}
</div>
</div>
);
}