89 lines
3.4 KiB
TypeScript
89 lines
3.4 KiB
TypeScript
import React, { useState, useCallback } from 'react';
|
|
import { type Service } from './types';
|
|
import { useLocalStorage } from './hooks/useLocalStorage';
|
|
import { PRECONFIGURED_SERVICES } from './constants';
|
|
import ServiceCard from './components/ServiceCard';
|
|
import AddServiceModal from './components/AddServiceModal';
|
|
import Header from './components/Header';
|
|
import ScanInfoModal from './components/ScanInfoModal';
|
|
import { Plus } from 'lucide-react';
|
|
import { Globe } from 'lucide-react';
|
|
|
|
const App: React.FC = () => {
|
|
const [services, setServices] = useLocalStorage<Service[]>('dashboard-services', PRECONFIGURED_SERVICES);
|
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
const [isScanInfoModalOpen, setIsScanInfoModalOpen] = useState(false);
|
|
|
|
const addService = useCallback((newService: Omit<Service, 'id'>) => {
|
|
setServices(prevServices => [
|
|
...prevServices,
|
|
{ ...newService, id: Date.now().toString() }
|
|
]);
|
|
}, [setServices]);
|
|
|
|
const addDiscoveredServices = useCallback((discoveredServices: Omit<Service, 'id'>[]) => {
|
|
setServices(prevServices => {
|
|
const existingUrls = new Set(prevServices.map(s => s.url));
|
|
const newServices = discoveredServices
|
|
.filter(ds => !existingUrls.has(ds.url))
|
|
.map(ds => ({
|
|
...ds,
|
|
id: `${ds.name}-${ds.url}-${Math.random()}`, // Create a unique ID
|
|
icon: Globe, // Default icon for discovered services
|
|
}));
|
|
|
|
return [...prevServices, ...newServices];
|
|
});
|
|
}, [setServices]);
|
|
|
|
const deleteService = useCallback((id: string) => {
|
|
setServices(prevServices => prevServices.filter(service => service.id !== id));
|
|
}, [setServices]);
|
|
|
|
return (
|
|
<div className="min-h-screen bg-gray-900 text-gray-100 font-sans">
|
|
<Header
|
|
onAddService={() => setIsModalOpen(true)}
|
|
onScanNetwork={() => setIsScanInfoModalOpen(true)}
|
|
/>
|
|
|
|
<main className="container mx-auto p-4 md:p-8">
|
|
{services.length > 0 ? (
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-6">
|
|
{services.map(service => (
|
|
<ServiceCard key={service.id} service={service} onDelete={deleteService} />
|
|
))}
|
|
</div>
|
|
) : (
|
|
<div className="text-center py-20">
|
|
<h2 className="text-2xl font-semibold text-gray-400">Your Dashboard is Empty</h2>
|
|
<p className="text-gray-500 mt-2">Click the button below to start adding your local services.</p>
|
|
<button
|
|
onClick={() => setIsModalOpen(true)}
|
|
className="mt-6 inline-flex items-center px-6 py-3 border border-transparent text-base font-medium rounded-md shadow-sm text-white bg-teal-500 hover:bg-teal-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-900 focus:ring-teal-500 transition-colors"
|
|
>
|
|
<Plus className="-ml-1 mr-3 h-5 w-5" />
|
|
Add Your First Service
|
|
</button>
|
|
</div>
|
|
)}
|
|
</main>
|
|
|
|
{isModalOpen && (
|
|
<AddServiceModal
|
|
onClose={() => setIsModalOpen(false)}
|
|
onAddService={addService}
|
|
/>
|
|
)}
|
|
|
|
{isScanInfoModalOpen && (
|
|
<ScanInfoModal
|
|
onClose={() => setIsScanInfoModalOpen(false)}
|
|
onScanComplete={addDiscoveredServices}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default App; |