Files
star-erp/resources/js/Pages/System/Manual/Index.tsx
sky121113 29cdf37b71
All checks were successful
Koori-ERP-Deploy-System / deploy-demo (push) Has been skipped
Koori-ERP-Deploy-System / deploy-production (push) Successful in 56s
style: 簡化操作手冊標題為『操作指南』
2026-02-13 16:04:04 +08:00

160 lines
9.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Head, Link } from "@inertiajs/react";
import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import {
BookOpen,
Search,
Menu,
FileText,
HelpCircle
} from "lucide-react";
import { useState } from "react";
import { ScrollArea } from "@/Components/ui/scroll-area";
import { Input } from "@/Components/ui/input";
import { cn } from "@/lib/utils";
interface Page {
title: string;
slug: string;
}
interface Section {
title: string;
pages: Page[];
}
interface Props {
toc: Section[];
currentSlug: string;
content: string;
}
export default function ManualIndex({ toc, currentSlug, content }: Props) {
const [searchQuery, setSearchQuery] = useState("");
const [isSidebarOpen, setIsSidebarOpen] = useState(true);
// Filter TOC based on search
const filteredToc = toc.map(section => ({
...section,
pages: section.pages.filter(page =>
page.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
section.title.toLowerCase().includes(searchQuery.toLowerCase())
)
})).filter(section => section.pages.length > 0);
return (
<AuthenticatedLayout breadcrumbs={[
{ label: "系統管理", href: "#" },
{ label: "操作手冊", href: route('system.manual.index'), isPage: true }
]}>
<Head title="操作手冊" />
<div className="flex h-[calc(100vh-140px)] bg-slate-50/50 rounded-xl border border-slate-200 shadow-sm overflow-hidden m-2 md:m-6">
{/* Sidebar */}
<aside className={cn(
"w-72 border-r border-slate-200 bg-white flex flex-col transition-all duration-300",
!isSidebarOpen && "w-0 opacity-0 overflow-hidden"
)}>
<div className="p-5 border-b border-slate-100 bg-slate-50/30">
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-slate-400" />
<Input
placeholder="搜尋功能手冊..."
className="pl-10 h-10 bg-white border-slate-200 focus:ring-2 focus:ring-primary-lighter transition-all placeholder:text-slate-400"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</div>
</div>
<ScrollArea className="flex-1">
<div className="p-4 space-y-8">
{filteredToc.map((section, idx) => (
<div key={idx} className="space-y-2">
<h3 className="px-3 text-[11px] font-bold text-slate-400 uppercase tracking-[0.1em]">
{section.title}
</h3>
<div className="space-y-1">
{section.pages.map((page) => (
<Link
key={page.slug}
href={route('system.manual.index', { slug: page.slug })}
className={cn(
"flex items-center gap-3 px-3 py-2.5 text-sm font-medium rounded-lg transition-all",
currentSlug === page.slug
? "bg-primary-main text-white shadow-md shadow-primary-main/20"
: "text-slate-600 hover:bg-slate-100 hover:text-slate-900"
)}
>
<FileText className={cn("h-4 w-4 shrink-0", currentSlug === page.slug ? "text-white" : "text-slate-400")} />
<span className="truncate">{page.title}</span>
</Link>
))}
</div>
</div>
))}
</div>
</ScrollArea>
</aside>
{/* Main Content */}
<main className="flex-1 flex flex-col min-w-0 bg-white">
{/* Content Header mobile toggle */}
<div className="h-14 shrink-0 border-b border-slate-100 flex items-center px-6 gap-3 bg-white/80 backdrop-blur-md sticky top-0 z-10">
<button
onClick={() => setIsSidebarOpen(!isSidebarOpen)}
className="p-2 hover:bg-slate-100 rounded-lg text-slate-500 transition-colors border border-transparent hover:border-slate-200"
title={isSidebarOpen ? "收起選單" : "展開選單"}
>
<Menu className="h-5 w-5" />
</button>
<div className="h-5 w-px bg-slate-200 mx-1" />
<div className="flex items-center gap-2 text-slate-800">
<BookOpen className="h-5 w-5 text-primary-main" />
<span className="text-sm font-bold tracking-tight"></span>
</div>
</div>
<div className="flex-1 overflow-y-auto bg-white scroll-smooth" id="manual-content-scroll">
<style dangerouslySetInnerHTML={{
__html: `
#manual-article { font-size: 15px; line-height: 1.6; color: #475569; }
#manual-article h1 { font-size: 1.75rem; margin-top: 0 !important; margin-bottom: 0.75rem !important; padding-bottom: 0.5rem; border-bottom: 1px solid #f1f5f9; font-weight: 800; color: #0f172a; }
#manual-article h2 { font-size: 1.25rem; margin-top: 1.25rem !important; margin-bottom: 0.5rem !important; font-weight: 700; color: #1e293b; }
#manual-article h3 { font-size: 1.1rem; margin-top: 1rem !important; margin-bottom: 0.4rem !important; font-weight: 600; color: #334155; }
#manual-article p { margin-top: 0.4rem !important; margin-bottom: 0.4rem !important; }
#manual-article ul, #manual-article ol { margin-top: 0.4rem !important; margin-bottom: 0.4rem !important; padding-left: 1.25rem; }
#manual-article li { margin-top: 0.2rem !important; margin-bottom: 0.2rem !important; }
#manual-article blockquote { margin: 0.75rem 0 !important; padding: 0.25rem 1rem !important; border-left: 4px solid var(--primary-main); background: #f8fafc; border-radius: 0 4px 4px 0; }
#manual-article img { margin: 1rem 0 !important; border-radius: 8px; box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1); }
#manual-article code { padding: 0.1rem 0.3rem; background: #e6f7f3; color: #018a6a; border-radius: 4px; font-size: 0.85em; font-family: ui-monospace, monospace; }
#manual-article pre { margin: 0.75rem 0 !important; padding: 0.75rem !important; background: #1e293b; color: #f8fafc; border-radius: 8px; overflow-x: auto; }
`}} />
<div className="max-w-4xl mx-auto p-4 md:p-10 lg:p-12">
<article id="manual-article" className="prose prose-slate max-w-none">
<ReactMarkdown remarkPlugins={[remarkGfm]}>
{content}
</ReactMarkdown>
</article>
<div className="mt-16 pt-8 border-t border-slate-100 flex flex-col md:flex-row items-center justify-between gap-6 text-sm">
<div className="flex items-center gap-3 px-4 py-2 bg-slate-50 rounded-full border border-slate-200">
<HelpCircle className="h-4 w-4 text-primary-main" />
<span className="text-slate-600 font-medium whitespace-nowrap"> (分機: 8888)</span>
</div>
<div className="flex items-center gap-4 text-slate-400">
<span>最後更新: 2026-02-13</span>
<span className="h-1 w-1 bg-slate-300 rounded-full" />
<span className="font-semibold tracking-widest uppercase">Star ERP v1.0</span>
</div>
</div>
</div>
</div>
</main>
</div>
</AuthenticatedLayout>
);
}