183 lines
4.9 KiB
JavaScript
183 lines
4.9 KiB
JavaScript
import { useEffect, useState } from 'react';
|
|
import './App.css';
|
|
import SectionGrid from './components/SectionGrid';
|
|
import AppModal from './components/AppModal';
|
|
import ConfirmDialog from './components/ConfirmDialog';
|
|
import Clock from './components/Clock';
|
|
import { ToastContainer, toast } from 'react-toastify';
|
|
import 'react-toastify/dist/ReactToastify.css';
|
|
import { IoIosAdd } from 'react-icons/io';
|
|
import CustomToast from './components/CustomToast';
|
|
import {
|
|
fetchSections,
|
|
addAppToSection,
|
|
editAppInSection,
|
|
deleteAppFromSection,
|
|
} from './services/api';
|
|
|
|
function App() {
|
|
const [sections, setSections] = useState([]);
|
|
const [editData, setEditData] = useState(null);
|
|
const [showAdd, setShowAdd] = useState(false);
|
|
const [confirmData, setConfirmData] = useState(null);
|
|
const [searchTerm, setSearchTerm] = useState('');
|
|
|
|
const loadSections = () => {
|
|
fetchSections()
|
|
.then(data => {
|
|
const filtered = (data.sections || []).filter(section => (section.apps?.length ?? 0) > 0);
|
|
setSections(filtered);
|
|
})
|
|
.catch(err => console.error('Failed to fetch sections:', err));
|
|
};
|
|
|
|
useEffect(() => {
|
|
loadSections();
|
|
}, []);
|
|
|
|
const handleDelete = (app) => {
|
|
setConfirmData({
|
|
message: `Are you sure you want to delete "${app.name}"?`,
|
|
app,
|
|
});
|
|
};
|
|
|
|
const confirmDelete = async () => {
|
|
if (!confirmData?.app) return;
|
|
|
|
try {
|
|
await deleteAppFromSection({
|
|
section: confirmData.app.section,
|
|
app: confirmData.app,
|
|
});
|
|
toast(<CustomToast type="delete" message={`App "${confirmData.app.name}" deleted successfully!`} />);
|
|
loadSections();
|
|
} catch (err) {
|
|
console.error('Failed to delete app:', err);
|
|
toast.error('Failed to delete app');
|
|
} finally {
|
|
setConfirmData(null);
|
|
}
|
|
};
|
|
|
|
const handleAddSubmit = async (data) => {
|
|
try {
|
|
await addAppToSection(data);
|
|
toast(<CustomToast type="success" message={`App "${data.app.name}" added successfully!`} />);
|
|
setShowAdd(false);
|
|
loadSections();
|
|
} catch (err) {
|
|
console.error('Failed to add app:', err);
|
|
toast.error('Failed to add app');
|
|
}
|
|
};
|
|
|
|
const handleEditSubmit = async (data) => {
|
|
try {
|
|
await editAppInSection({
|
|
section: data.section,
|
|
app: data.app,
|
|
original_name: data.original_name || data.app.name,
|
|
});
|
|
toast(<CustomToast type="edit" message={`App "${data.app.name}" updated successfully!`} />);
|
|
setEditData(null);
|
|
loadSections();
|
|
} catch (err) {
|
|
console.error('Failed to update app:', err);
|
|
toast.error('Failed to update app');
|
|
}
|
|
};
|
|
|
|
const filteredSections = sections.map(section => ({
|
|
...section,
|
|
apps: section.apps.filter(app =>
|
|
app.name.toLowerCase().includes(searchTerm.toLowerCase())
|
|
),
|
|
})).filter(section => section.apps.length > 0);
|
|
|
|
return (
|
|
<div className="App">
|
|
<Clock />
|
|
<h1 className="main-title">
|
|
<img src="/navix-logo.svg" alt="Navix logo" className="navix-logo" />
|
|
Navix
|
|
</h1>
|
|
|
|
{/* 🔍 Search Box */}
|
|
<div style={{ display: 'flex', justifyContent: 'flex-start', marginLeft: '2rem' }}>
|
|
<input
|
|
type="text"
|
|
placeholder="Search apps..."
|
|
value={searchTerm}
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
className="search-input"
|
|
/>
|
|
</div>
|
|
|
|
{showAdd && (
|
|
<AppModal
|
|
mode="add"
|
|
onSubmit={handleAddSubmit}
|
|
onClose={() => setShowAdd(false)}
|
|
sections={sections}
|
|
/>
|
|
)}
|
|
|
|
{editData && (
|
|
<AppModal
|
|
mode="edit"
|
|
initialData={editData}
|
|
onSubmit={handleEditSubmit}
|
|
onClose={() => setEditData(null)}
|
|
sections={sections}
|
|
/>
|
|
)}
|
|
|
|
{confirmData && (
|
|
<ConfirmDialog
|
|
message={confirmData.message}
|
|
onConfirm={confirmDelete}
|
|
onCancel={() => setConfirmData(null)}
|
|
/>
|
|
)}
|
|
|
|
{filteredSections.map((section) => (
|
|
<SectionGrid
|
|
key={section.name}
|
|
section={section}
|
|
onEdit={(app) =>
|
|
setEditData({ ...app, section: section.name, original_name: app.name })
|
|
}
|
|
onDelete={handleDelete}
|
|
/>
|
|
))}
|
|
|
|
<IoIosAdd className="add-button" onClick={() => setShowAdd(true)} />
|
|
|
|
<ToastContainer
|
|
position="bottom-right"
|
|
autoClose={3000}
|
|
hideProgressBar={false}
|
|
newestOnTop
|
|
closeOnClick
|
|
pauseOnFocusLoss
|
|
draggable
|
|
pauseOnHover
|
|
theme="dark"
|
|
toastStyle={{
|
|
backgroundColor: 'black',
|
|
borderRadius: '12px',
|
|
minHeight: '110px',
|
|
padding: '20px',
|
|
color: '#fff',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
}}
|
|
bodyClassName="toast-body"
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default App;
|