feat: add LMS web client
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
import { Download, MapPinned, Play, RefreshCw, Square } from 'lucide-react'
|
||||
import type React from 'react'
|
||||
|
||||
export type ModuleFilter = 'all' | 'running' | 'stopped' | 'uninstalled'
|
||||
|
||||
type ModuleListToolbarProps = {
|
||||
dark: boolean
|
||||
filter: ModuleFilter
|
||||
loading: boolean
|
||||
installingModuleName: string | null
|
||||
uninstalledCount: number
|
||||
runningCount: number
|
||||
stoppedCount: number
|
||||
onFilterChange: (filter: ModuleFilter) => void
|
||||
onInstallAll: () => void
|
||||
onStartAll: () => void
|
||||
onStopAll: () => void
|
||||
}
|
||||
|
||||
const FILTER_TABS: Array<{ id: ModuleFilter; label: string }> = [
|
||||
{ id: 'all', label: '전체' },
|
||||
{ id: 'running', label: '실행중' },
|
||||
{ id: 'stopped', label: '정지됨' },
|
||||
{ id: 'uninstalled', label: '미설치' }
|
||||
]
|
||||
|
||||
export default function ModuleListToolbar({
|
||||
dark,
|
||||
filter,
|
||||
loading,
|
||||
installingModuleName,
|
||||
uninstalledCount,
|
||||
runningCount,
|
||||
stoppedCount,
|
||||
onFilterChange,
|
||||
onInstallAll,
|
||||
onStartAll,
|
||||
onStopAll
|
||||
}: ModuleListToolbarProps): React.JSX.Element {
|
||||
const installingAll = installingModuleName === '__all__'
|
||||
const startingAll = installingModuleName === '__start_all__'
|
||||
const stoppingAll = installingModuleName === '__stop_all__'
|
||||
|
||||
const bulkActions = [
|
||||
{ label: '맵설정', Icon: MapPinned, busy: false, disabled: false, onClick: undefined },
|
||||
{
|
||||
label: '전체 설치',
|
||||
Icon: Download,
|
||||
busy: installingAll,
|
||||
disabled: loading || installingModuleName !== null || uninstalledCount === 0,
|
||||
onClick: onInstallAll
|
||||
},
|
||||
{
|
||||
label: '전체 실행',
|
||||
Icon: Play,
|
||||
busy: startingAll,
|
||||
disabled: loading || installingModuleName !== null || stoppedCount === 0,
|
||||
onClick: onStartAll
|
||||
},
|
||||
{
|
||||
label: '전체 중지',
|
||||
Icon: Square,
|
||||
busy: stoppingAll,
|
||||
disabled: loading || installingModuleName !== null || runningCount === 0,
|
||||
onClick: onStopAll
|
||||
}
|
||||
]
|
||||
|
||||
return (
|
||||
<div className={['flex h-10 items-end justify-between px-3', dark ? 'bg-[#252525]' : 'bg-[#f7f8fb]'].join(' ')}>
|
||||
<div className="flex items-end">
|
||||
{FILTER_TABS.map(({ id, label }) => {
|
||||
const active = filter === id
|
||||
|
||||
return (
|
||||
<button
|
||||
key={id}
|
||||
type="button"
|
||||
onClick={() => onFilterChange(id)}
|
||||
className={[
|
||||
'h-8 min-w-[76px] cursor-pointer border-x border-t px-3 text-[12px] font-bold transition first:rounded-tl-md last:rounded-tr-md',
|
||||
active
|
||||
? dark
|
||||
? 'border-white/10 bg-[#2b2b2b] text-white'
|
||||
: 'border-[#e1e6ef] bg-white text-[#111520]'
|
||||
: dark
|
||||
? 'border-white/8 bg-[#202020] text-white/45 hover:text-white/70'
|
||||
: 'border-[#e1e6ef] bg-[#eef1f6] text-[#687284] hover:text-[#111520]'
|
||||
].join(' ')}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 pb-2">
|
||||
{bulkActions.map(({ label, Icon, busy, disabled, onClick }) => (
|
||||
<button
|
||||
key={label}
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
onClick={() => {
|
||||
if (!onClick) return
|
||||
onClick()
|
||||
}}
|
||||
className={[
|
||||
'flex h-7 items-center gap-1.5 rounded-md border px-3 text-[12px] font-bold transition',
|
||||
disabled ? 'cursor-default opacity-45' : 'cursor-pointer',
|
||||
dark
|
||||
? 'border-white/15 bg-white/[0.04] text-white/70 hover:border-white/25 hover:bg-white/10 hover:text-white'
|
||||
: 'border-[#cfd6e4] bg-white text-[#4f596b] hover:bg-[#eef1f7] hover:text-[#111520]'
|
||||
].join(' ')}
|
||||
>
|
||||
{busy ? <RefreshCw size={14} className="animate-spin" /> : <Icon size={14} />}
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user