121 lines
5.8 KiB
TypeScript
121 lines
5.8 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useMemo, useState } from "react";
|
|
import Link from "next/link";
|
|
import { BookOpen, Cpu, Factory, FileText, StickyNote } from "lucide-react";
|
|
|
|
import SearchInput from "@/components/common/SearchInput";
|
|
import SummaryCard from "@/components/common/SummaryCard";
|
|
import { buttonVariants } from "@/components/ui/button";
|
|
import { api } from "@/lib/api";
|
|
import type { KnowledgeDevice, KnowledgeDocument, KnowledgeManufacturer, KnowledgeNote, KnowledgeSearchResult } from "@/types/knowledge";
|
|
|
|
export default function KnowledgePage() {
|
|
const [manufacturers, setManufacturers] = useState<KnowledgeManufacturer[]>([]);
|
|
const [devices, setDevices] = useState<KnowledgeDevice[]>([]);
|
|
const [documents, setDocuments] = useState<KnowledgeDocument[]>([]);
|
|
const [notes, setNotes] = useState<KnowledgeNote[]>([]);
|
|
const [results, setResults] = useState<KnowledgeSearchResult[]>([]);
|
|
const [query, setQuery] = useState("");
|
|
const [error, setError] = useState("");
|
|
|
|
useEffect(() => {
|
|
queueMicrotask(() => {
|
|
Promise.all([
|
|
api.get<KnowledgeManufacturer[]>("/knowledge/manufacturers?limit=100"),
|
|
api.get<KnowledgeDevice[]>("/knowledge/devices?limit=100"),
|
|
api.get<KnowledgeDocument[]>("/knowledge/documents?limit=10"),
|
|
api.get<KnowledgeNote[]>("/knowledge/notes?limit=10"),
|
|
]).then(([manufacturerResponse, deviceResponse, documentResponse, noteResponse]) => {
|
|
setManufacturers(manufacturerResponse.data);
|
|
setDevices(deviceResponse.data);
|
|
setDocuments(documentResponse.data);
|
|
setNotes(noteResponse.data);
|
|
}).catch(() => setError("Wissensdatenbank konnte nicht geladen werden"));
|
|
});
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const trimmed = query.trim();
|
|
if (!trimmed) {
|
|
queueMicrotask(() => setResults([]));
|
|
return;
|
|
}
|
|
const timeout = window.setTimeout(() => {
|
|
api.get<KnowledgeSearchResult[]>(`/knowledge/search?q=${encodeURIComponent(trimmed)}`)
|
|
.then((response) => setResults(response.data))
|
|
.catch(() => setResults([]));
|
|
}, 250);
|
|
return () => window.clearTimeout(timeout);
|
|
}, [query]);
|
|
|
|
const latestDocuments = useMemo(() => documents.slice(0, 5), [documents]);
|
|
const latestNotes = useMemo(() => notes.slice(0, 5), [notes]);
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
|
<div>
|
|
<h1 className="text-3xl font-bold text-slate-950">Wissensdatenbank</h1>
|
|
<p className="mt-1 text-sm text-slate-500">Serviceunterlagen, Modelle, Schaltpläne und Reparaturwissen</p>
|
|
</div>
|
|
<div className="flex flex-wrap gap-2">
|
|
<Link className={buttonVariants({ variant: "outline" })} href="/knowledge/manufacturers"><Factory size={16} />Hersteller</Link>
|
|
<Link className={buttonVariants({ variant: "outline" })} href="/knowledge/devices"><Cpu size={16} />Geräte</Link>
|
|
<Link className={buttonVariants({ variant: "outline" })} href="/knowledge/documents"><FileText size={16} />Dokumente</Link>
|
|
<Link className={buttonVariants({ variant: "outline" })} href="/knowledge/notes"><StickyNote size={16} />Notizen</Link>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid gap-4 md:grid-cols-4">
|
|
<SummaryCard label="Hersteller" value={manufacturers.length} />
|
|
<SummaryCard label="Geräte" value={devices.length} />
|
|
<SummaryCard label="Dokumente" value={documents.length} />
|
|
<SummaryCard label="Notizen" value={notes.length} />
|
|
</div>
|
|
|
|
<section className="rounded-lg border bg-white p-4">
|
|
<SearchInput value={query} onChange={setQuery} placeholder="Hersteller, Modell, Dokument oder Notiz suchen" />
|
|
{results.length > 0 && (
|
|
<div className="mt-4 divide-y rounded-lg border">
|
|
{results.map((result) => (
|
|
<Link key={`${result.entity_type}-${result.id}`} href={result.href} className="flex items-center gap-3 p-3 hover:bg-slate-50">
|
|
<BookOpen size={16} className="text-slate-500" />
|
|
<span className="font-medium text-slate-950">{result.title}</span>
|
|
<span className="text-sm text-slate-500">{result.subtitle}</span>
|
|
</Link>
|
|
))}
|
|
</div>
|
|
)}
|
|
{error && <p className="mt-3 text-sm text-red-600">{error}</p>}
|
|
</section>
|
|
|
|
<div className="grid gap-6 lg:grid-cols-2">
|
|
<section className="rounded-lg border bg-white p-5">
|
|
<h2 className="text-lg font-semibold text-slate-950">Letzte Dokumente</h2>
|
|
<div className="mt-4 space-y-3">
|
|
{latestDocuments.map((document) => (
|
|
<Link key={document.id} href={`/knowledge/documents/${document.id}`} className="block rounded-lg border p-3 hover:bg-slate-50">
|
|
<p className="font-medium">{document.title}</p>
|
|
<p className="text-sm text-slate-500">{document.manufacturer.name}{document.device ? ` · ${document.device.name}` : ""}</p>
|
|
</Link>
|
|
))}
|
|
{latestDocuments.length === 0 && <p className="text-sm text-slate-500">Noch keine Dokumente vorhanden.</p>}
|
|
</div>
|
|
</section>
|
|
<section className="rounded-lg border bg-white p-5">
|
|
<h2 className="text-lg font-semibold text-slate-950">Letzte Notizen</h2>
|
|
<div className="mt-4 space-y-3">
|
|
{latestNotes.map((note) => (
|
|
<Link key={note.id} href="/knowledge/notes" className="block rounded-lg border p-3 hover:bg-slate-50">
|
|
<p className="font-medium">{note.title}</p>
|
|
<p className="line-clamp-2 text-sm text-slate-500">{note.content}</p>
|
|
</Link>
|
|
))}
|
|
{latestNotes.length === 0 && <p className="text-sm text-slate-500">Noch keine Notizen vorhanden.</p>}
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|