Files
Nexus_Mat/pages/index.tsx

163 lines
6.8 KiB
TypeScript

import React, { useState, useEffect, useCallback } from 'react';
import Head from 'next/head';
import { Navbar } from '../components/Navbar';
import { MaterialCard } from '../components/MaterialCard';
import { MaterialDetail } from '../components/MaterialDetail';
import { CreateModal } from '../components/CreateModal';
import { ProfileModal } from '../components/ProfileModal';
import { DataService } from '../services/dataService';
import { MaterialDTO, UserDTO, MaterialType } from '../types';
import { Filter, Grid } from 'lucide-react';
export default function Home() {
const [materials, setMaterials] = useState<MaterialDTO[]>([]);
const [page, setPage] = useState(1);
const pageSize = 20;
const [total, setTotal] = useState(0);
const [hasNext, setHasNext] = useState(false);
const [currentUser, setCurrentUser] = useState<UserDTO | null>(null);
const [selectedId, setSelectedId] = useState<string | null>(null);
const [isCreateOpen, setIsCreateOpen] = useState(false);
const [isProfileOpen, setIsProfileOpen] = useState(false);
const [filter, setFilter] = useState<'ALL' | 'CODE' | 'VIDEO' | 'ASSET_ZIP'>('ALL');
const [searchQuery, setSearchQuery] = useState('');
const loadMaterials = useCallback(async () => {
try {
const filterKey: MaterialType | 'ALL' = filter === 'ALL' ? 'ALL' : filter as any;
const result = await DataService.getMaterials(page, pageSize, filterKey, searchQuery);
setMaterials(result.items);
setTotal(result.total);
setHasNext(result.hasNext);
} catch (e) {
console.error("Failed to load materials", e);
}
}, [page, filter, searchQuery]);
const refreshUser = useCallback(async () => {
const user = await DataService.getCurrentUser();
setCurrentUser(user);
}, []);
useEffect(() => {
const run = async () => {
await refreshUser();
await loadMaterials();
};
run();
}, [loadMaterials, refreshUser]);
const filteredMaterials = materials;
return (
<div className="min-h-screen pb-20">
<Head>
<title>NEXUS_MAT.OS</title>
</Head>
<Navbar
user={currentUser}
onOpenCreate={() => setIsCreateOpen(true)}
onProfileClick={() => setIsProfileOpen(true)}
searchQuery={searchQuery}
onSearch={(q) => { setSearchQuery(q); setPage(1); }}
/>
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pt-8">
{/* Header / Filter Section */}
<div className="flex flex-col md:flex-row md:items-center justify-between mb-12 gap-6">
<div>
<h1 className="text-4xl md:text-5xl font-bold text-white font-mono tracking-tighter mb-2">
GRID_ACCESS
</h1>
<p className="text-gray-500 font-mono text-sm">
INDEXING {total} RESOURCES FROM THE NETWORK
</p>
</div>
<div className="flex items-center gap-4 bg-cyber-panel/30 p-2 rounded-lg border border-white/5 backdrop-blur-sm">
<Filter size={16} className="text-cyber-neon ml-2" />
<div className="flex gap-2">
{(['ALL', 'CODE', 'VIDEO', 'ASSET_ZIP'] as const).map(f => (
<button
key={f}
onClick={() => { setFilter(f); setPage(1); }}
className={`px-3 py-1 text-xs font-mono rounded transition-all ${filter === f
? 'bg-cyber-neon text-black font-bold'
: 'text-gray-400 hover:text-white'
}`}
>
{f}
</button>
))}
</div>
<div className="w-[1px] h-6 bg-gray-700 mx-2"></div>
<Grid size={16} className="text-gray-400" />
</div>
</div>
{/* The Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{filteredMaterials.map(m => (
<MaterialCard
key={m.id}
material={m}
onClick={(id) => setSelectedId(id)}
currentUser={currentUser}
/>
))}
</div>
{/* Pagination */}
<div className="mt-8 flex items-center justify-center gap-4">
<button onClick={() => setPage(p => Math.max(1, p - 1))} className="px-3 py-2 border border-white/10 text-gray-400 hover:text-white">Prev</button>
<span className="text-xs font-mono text-gray-500">PAGE {page} / {Math.max(1, Math.ceil(total / pageSize))}</span>
<button
onClick={() => setPage(p => p + 1)}
disabled={!hasNext}
className="px-3 py-2 border border-white/10 text-gray-400 hover:text-white disabled:opacity-50"
>Next</button>
</div>
{/* Empty State */}
{filteredMaterials.length === 0 && (
<div className="text-center py-20 border border-dashed border-gray-800 rounded-xl">
<p className="text-gray-600 font-mono">NO DATA FOUND IN SECTOR.</p>
</div>
)}
</main>
{/* Modals */}
{selectedId && (
<MaterialDetail
id={selectedId}
onClose={() => setSelectedId(null)}
currentUser={currentUser}
/>
)}
{isCreateOpen && (
<CreateModal
onClose={() => setIsCreateOpen(false)}
onSuccess={() => {
setIsCreateOpen(false);
loadMaterials();
}}
/>
)}
{isProfileOpen && currentUser && (
<ProfileModal
user={currentUser}
onClose={() => setIsProfileOpen(false)}
onUpdate={refreshUser}
/>
)}
{/* Footer Decoration */}
<footer className="fixed bottom-0 left-0 w-full h-1 bg-gradient-to-r from-cyber-neon via-purple-600 to-cyber-blue opacity-50"></footer>
</div>
);
}