feat(knowledge): add service library
This commit is contained in:
parent
ecab0fe6b6
commit
228da8f814
32 changed files with 2878 additions and 6 deletions
85
frontend/athena/app/knowledge/devices/[id]/page.tsx
Normal file
85
frontend/athena/app/knowledge/devices/[id]/page.tsx
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { useParams } from "next/navigation";
|
||||
import { Download } from "lucide-react";
|
||||
|
||||
import DetailSection from "@/components/common/DetailSection";
|
||||
import DocumentTypeBadge from "@/components/knowledge/DocumentTypeBadge";
|
||||
import TagList from "@/components/knowledge/TagList";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { api } from "@/lib/api";
|
||||
import type { KnowledgeDevice, KnowledgeDocument, KnowledgeNote } from "@/types/knowledge";
|
||||
|
||||
export default function KnowledgeDeviceDetailPage() {
|
||||
const params = useParams<{ id: string }>();
|
||||
const [device, setDevice] = useState<KnowledgeDevice | null>(null);
|
||||
const [documents, setDocuments] = useState<KnowledgeDocument[]>([]);
|
||||
const [notes, setNotes] = useState<KnowledgeNote[]>([]);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all([
|
||||
api.get<KnowledgeDevice>(`/knowledge/devices/${params.id}`),
|
||||
api.get<KnowledgeDocument[]>(`/knowledge/documents?device_id=${params.id}&limit=100`),
|
||||
api.get<KnowledgeNote[]>(`/knowledge/notes?device_id=${params.id}&limit=100`),
|
||||
]).then(([deviceResponse, documentResponse, noteResponse]) => {
|
||||
setDevice(deviceResponse.data);
|
||||
setDocuments(documentResponse.data);
|
||||
setNotes(noteResponse.data);
|
||||
}).catch(() => setError("Gerät konnte nicht geladen werden"));
|
||||
}, [params.id]);
|
||||
|
||||
const yearRange = useMemo(() => {
|
||||
if (!device) return "-";
|
||||
if (!device.production_year_from && !device.production_year_to) return "-";
|
||||
return `${device.production_year_from ?? "?"} - ${device.production_year_to ?? "?"}`;
|
||||
}, [device]);
|
||||
|
||||
if (error) return <p className="text-red-600">{error}</p>;
|
||||
if (!device) return <p className="text-slate-500">Gerät wird geladen...</p>;
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-slate-950">{device.name}</h1>
|
||||
<p className="mt-1 text-sm text-slate-500">{device.manufacturer.name}{device.model_number ? ` · ${device.model_number}` : ""}</p>
|
||||
</div>
|
||||
<DetailSection title="Stammdaten">
|
||||
<dl className="grid gap-3 text-sm md:grid-cols-2">
|
||||
<div><dt className="text-slate-500">Gerätetyp</dt><dd className="font-medium">{device.device_type || "-"}</dd></div>
|
||||
<div><dt className="text-slate-500">Frequenzbereich</dt><dd className="font-medium">{device.frequency_range || "-"}</dd></div>
|
||||
<div><dt className="text-slate-500">Baujahre</dt><dd className="font-medium">{yearRange}</dd></div>
|
||||
<div><dt className="text-slate-500">Slug</dt><dd className="font-medium">{device.slug}</dd></div>
|
||||
</dl>
|
||||
{device.notes && <p className="mt-4 whitespace-pre-wrap text-sm text-slate-700">{device.notes}</p>}
|
||||
</DetailSection>
|
||||
<DetailSection title="Dokumente">
|
||||
<div className="space-y-3">
|
||||
{documents.map((document) => (
|
||||
<div key={document.id} className="flex items-center justify-between gap-3 rounded-lg border p-3">
|
||||
<div>
|
||||
<Link href={`/knowledge/documents/${document.id}`} className="font-medium text-slate-950 hover:underline">{document.title}</Link>
|
||||
<div className="mt-2 flex flex-wrap items-center gap-2"><DocumentTypeBadge type={document.document_type} /><TagList tags={document.tags} /></div>
|
||||
</div>
|
||||
{document.file_name && <Link href={`/api/knowledge/documents/${document.id}/download`} className={buttonVariants({ variant: "outline", size: "icon-sm" })} title="Download"><Download size={16} /></Link>}
|
||||
</div>
|
||||
))}
|
||||
{documents.length === 0 && <p className="text-sm text-slate-500">Keine verknüpften Dokumente.</p>}
|
||||
</div>
|
||||
</DetailSection>
|
||||
<DetailSection title="Notizen">
|
||||
<div className="space-y-3">
|
||||
{notes.map((note) => (
|
||||
<div key={note.id} className="rounded-lg border p-3">
|
||||
<p className="font-medium">{note.title}</p>
|
||||
<p className="mt-1 whitespace-pre-wrap text-sm text-slate-600">{note.content}</p>
|
||||
</div>
|
||||
))}
|
||||
{notes.length === 0 && <p className="text-sm text-slate-500">Keine verknüpften Notizen.</p>}
|
||||
</div>
|
||||
</DetailSection>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue