feat(mcp): подготовить сервер и доступ к приёмке
This commit is contained in:
commit
bf3d50e1ff
3835 files changed
+537122
No files matched your search
@@ -0,0 +1,242 @@
|
||||
"use client"
|
||||
|
||||
import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
type ReactNode,
|
||||
} from "react"
|
||||
import { PanelLeftCloseIcon, ServerIcon } from "lucide-react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
} from "@/components/ui/sheet"
|
||||
import {
|
||||
ALL_SERVERS_ID,
|
||||
ServerTileRail,
|
||||
hostnameOf,
|
||||
type ServerTileItem,
|
||||
} from "@/components/server-tile-rail"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
/** Preview: https://reui.io/preview/base/app-shell-12 · https://reui.io/preview/base/list-9 */
|
||||
|
||||
const STORAGE_KEY = "mm:server-rail-collapsed"
|
||||
const DESKTOP_MQ = "(min-width: 768px)"
|
||||
|
||||
function readCollapsed(): boolean {
|
||||
try {
|
||||
return window.localStorage.getItem(STORAGE_KEY) === "1"
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function writeCollapsed(value: boolean) {
|
||||
try {
|
||||
window.localStorage.setItem(STORAGE_KEY, value ? "1" : "0")
|
||||
} catch {
|
||||
// quota / private mode
|
||||
}
|
||||
}
|
||||
|
||||
interface ServerRailContextValue {
|
||||
openRail: () => void
|
||||
expandRail: () => void
|
||||
collapsed: boolean
|
||||
selectedLabel: string
|
||||
}
|
||||
|
||||
const ServerRailContext = createContext<ServerRailContextValue | null>(null)
|
||||
|
||||
function useServerRail() {
|
||||
const ctx = useContext(ServerRailContext)
|
||||
if (!ctx) {
|
||||
throw new Error("ServerRailMobileButton must be used inside ServerRailLayout")
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
function ServerRailMobileButton() {
|
||||
const { openRail, expandRail, collapsed, selectedLabel } = useServerRail()
|
||||
return (
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className={cn("min-w-0 max-w-full",collapsed ? undefined : "md:hidden")}
|
||||
aria-label={collapsed ? "Показать список серверов" : "Серверы"}
|
||||
onClick={() => {
|
||||
if (window.matchMedia(DESKTOP_MQ).matches) {
|
||||
expandRail()
|
||||
return
|
||||
}
|
||||
openRail()
|
||||
}}
|
||||
>
|
||||
<ServerIcon aria-hidden="true" data-icon="inline-start" />
|
||||
<span className="min-w-0 truncate">{selectedLabel}</span>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
function ServerRailLayout({
|
||||
items,
|
||||
selectedId,
|
||||
onSelect,
|
||||
showAll = true,
|
||||
allCount = 0,
|
||||
showCount = true,
|
||||
showType = true,
|
||||
headerRight,
|
||||
loading = false,
|
||||
preserveSelection = false,
|
||||
extra,
|
||||
header,
|
||||
banner,
|
||||
contentClassName,
|
||||
children,
|
||||
}: {
|
||||
items: ServerTileItem[]
|
||||
selectedId: string
|
||||
onSelect: (id: string) => void
|
||||
showAll?: boolean
|
||||
allCount?: number
|
||||
showCount?: boolean
|
||||
showType?: boolean
|
||||
headerRight?: ReactNode
|
||||
loading?: boolean
|
||||
preserveSelection?: boolean
|
||||
extra?: ReactNode
|
||||
header?: ReactNode
|
||||
banner?: ReactNode
|
||||
contentClassName?: string
|
||||
children: ReactNode
|
||||
}) {
|
||||
const [railOpen, setRailOpen] = useState(false)
|
||||
const [collapsed, setCollapsed] = useState(false)
|
||||
const [hydrated, setHydrated] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled=false
|
||||
queueMicrotask(()=>{if(cancelled)return;setCollapsed(readCollapsed());setHydrated(true)})
|
||||
return()=>{cancelled=true}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!hydrated) return
|
||||
writeCollapsed(collapsed)
|
||||
}, [collapsed, hydrated])
|
||||
|
||||
const handleSelect = useCallback(
|
||||
(id: string) => {
|
||||
onSelect(id)
|
||||
setRailOpen(false)
|
||||
},
|
||||
[onSelect],
|
||||
)
|
||||
|
||||
const expandRail = useCallback(() => {
|
||||
setCollapsed(false)
|
||||
}, [])
|
||||
|
||||
const selectedLabel = useMemo(() => {
|
||||
if (showAll && selectedId === ALL_SERVERS_ID) return "Все серверы"
|
||||
const item = items.find((entry) => entry.id === selectedId)
|
||||
return item ? hostnameOf(item) : "Сервер"
|
||||
}, [items, selectedId, showAll])
|
||||
|
||||
const ctx = useMemo<ServerRailContextValue>(
|
||||
() => ({
|
||||
openRail: () => setRailOpen(true),
|
||||
expandRail,
|
||||
collapsed,
|
||||
selectedLabel,
|
||||
}),
|
||||
[collapsed, expandRail, selectedLabel],
|
||||
)
|
||||
|
||||
const railHeaderRight = (
|
||||
<>
|
||||
{headerRight}
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
className="hidden md:inline-flex"
|
||||
aria-label="Свернуть список серверов"
|
||||
onClick={() => setCollapsed(true)}
|
||||
>
|
||||
<PanelLeftCloseIcon />
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
|
||||
const rail = (
|
||||
<ServerTileRail
|
||||
items={items}
|
||||
selectedId={selectedId}
|
||||
onSelect={handleSelect}
|
||||
showAll={showAll}
|
||||
allCount={allCount}
|
||||
showCount={showCount}
|
||||
showType={showType}
|
||||
headerRight={railHeaderRight}
|
||||
loading={loading}
|
||||
preserveSelection={preserveSelection}
|
||||
className="min-h-0 flex-1"
|
||||
/>
|
||||
)
|
||||
|
||||
return (
|
||||
<ServerRailContext.Provider value={ctx}>
|
||||
<div className="flex h-full min-h-0 flex-col">
|
||||
{header}
|
||||
{banner}
|
||||
<div className="flex min-h-0 flex-1">
|
||||
{!collapsed ? (
|
||||
<aside className="hidden min-h-0 w-60 shrink-0 flex-col gap-3 p-3 pr-0 md:flex">
|
||||
{rail}
|
||||
{extra}
|
||||
</aside>
|
||||
) : null}
|
||||
<div className={cn("min-w-0 flex-1 overflow-y-auto p-4 md:p-6", contentClassName)}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
<Sheet open={railOpen} onOpenChange={setRailOpen}>
|
||||
<SheetContent side="left" className="flex w-72 flex-col gap-3 p-3" showCloseButton>
|
||||
<SheetHeader className="px-1 pt-1">
|
||||
<SheetTitle>Серверы</SheetTitle>
|
||||
</SheetHeader>
|
||||
<div className="flex min-h-0 flex-1 flex-col gap-3">
|
||||
<ServerTileRail
|
||||
items={items}
|
||||
selectedId={selectedId}
|
||||
onSelect={handleSelect}
|
||||
showAll={showAll}
|
||||
allCount={allCount}
|
||||
showCount={showCount}
|
||||
showType={showType}
|
||||
showHeader={false}
|
||||
headerRight={headerRight}
|
||||
loading={loading}
|
||||
preserveSelection={preserveSelection}
|
||||
className="min-h-0 flex-1"
|
||||
/>
|
||||
{extra}
|
||||
</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
</div>
|
||||
</ServerRailContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export { ServerRailLayout, ServerRailMobileButton }
|
||||
Reference in new issue
Block a user