feat: add My Scripts/Team Scripts tabs and Build button to Script Library
- Add language field to ScriptTemplateListItem frontend type - Add mine/shared params to scriptsApi.getTemplates - Update scriptGeneratorStore.loadTemplates to accept filter params - Reorganize ScriptLibraryPage with tab bar (My Scripts / Team Scripts) - Add "Build a New Script" button linking to /script-builder Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,8 @@ export const scriptsApi = {
|
||||
category_slug?: string
|
||||
search?: string
|
||||
tags?: string // Phase 3: comma-separated tag filter
|
||||
mine?: boolean
|
||||
shared?: boolean
|
||||
}): Promise<ScriptTemplateListItem[]> {
|
||||
const response = await apiClient.get<ScriptTemplateListItem[]>('/scripts/templates', { params })
|
||||
return response.data
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Terminal, Settings } from 'lucide-react'
|
||||
import { Terminal, Settings, Wand2 } from 'lucide-react'
|
||||
import { useScriptGeneratorStore } from '@/store/scriptGeneratorStore'
|
||||
import { usePermissions } from '@/hooks/usePermissions'
|
||||
import { ScriptFilterBar } from '@/components/scripts/ScriptFilterBar'
|
||||
@@ -8,10 +8,13 @@ import { ScriptTemplateList } from '@/components/scripts/ScriptTemplateList'
|
||||
import { ScriptConfigurePane } from '@/components/scripts/ScriptConfigurePane'
|
||||
import { ScriptPreview } from '@/components/scripts/ScriptPreview'
|
||||
|
||||
type LibraryTab = 'mine' | 'team'
|
||||
|
||||
export default function ScriptLibraryPage() {
|
||||
const [paneMode, setPaneMode] = useState<'browse' | 'configure'>('browse')
|
||||
// inputValue owned here so it survives Configure ↔ Browse transitions
|
||||
const [inputValue, setInputValue] = useState('')
|
||||
const [activeTab, setActiveTab] = useState<LibraryTab>('mine')
|
||||
|
||||
const loadCategories = useScriptGeneratorStore(s => s.loadCategories)
|
||||
const loadTemplates = useScriptGeneratorStore(s => s.loadTemplates)
|
||||
@@ -24,8 +27,18 @@ export default function ScriptLibraryPage() {
|
||||
const canGenerate = isEngineer
|
||||
|
||||
useEffect(() => {
|
||||
loadCategories().then(() => loadTemplates())
|
||||
}, [loadCategories, loadTemplates])
|
||||
loadCategories().then(() => {
|
||||
const filters = activeTab === 'mine' ? { mine: true } : { shared: true }
|
||||
loadTemplates(filters)
|
||||
})
|
||||
}, [loadCategories, loadTemplates, activeTab])
|
||||
|
||||
const onTabChange = (tab: LibraryTab) => {
|
||||
setActiveTab(tab)
|
||||
setInputValue('')
|
||||
setSearch('')
|
||||
setPaneMode('browse')
|
||||
}
|
||||
|
||||
const onClearSearch = () => {
|
||||
setInputValue('')
|
||||
@@ -42,23 +55,55 @@ export default function ScriptLibraryPage() {
|
||||
setPaneMode('browse')
|
||||
}
|
||||
|
||||
const tabClass = (tab: LibraryTab) =>
|
||||
tab === activeTab
|
||||
? 'border-b-2 border-primary text-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground'
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 p-6 h-full">
|
||||
{/* Page header */}
|
||||
<div>
|
||||
<h1 className="text-2xl font-heading font-bold text-foreground">Script Library</h1>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
Browse PowerShell templates, fill in parameters, and generate ready-to-run scripts.
|
||||
</p>
|
||||
{isEngineer && (
|
||||
<Link
|
||||
to="/scripts/manage"
|
||||
className="inline-flex items-center gap-1.5 text-xs text-primary bg-primary/10 hover:bg-primary/15 px-2.5 py-1 rounded-full transition-colors mt-2 group"
|
||||
>
|
||||
<Settings size={12} className="group-hover:rotate-90 transition-transform duration-300" />
|
||||
Manage Templates
|
||||
</Link>
|
||||
)}
|
||||
<div className="flex items-start justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-heading font-bold text-foreground">Script Library</h1>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
Browse PowerShell templates, fill in parameters, and generate ready-to-run scripts.
|
||||
</p>
|
||||
{isEngineer && (
|
||||
<Link
|
||||
to="/scripts/manage"
|
||||
className="inline-flex items-center gap-1.5 text-xs text-primary bg-primary/10 hover:bg-primary/15 px-2.5 py-1 rounded-full transition-colors mt-2 group"
|
||||
>
|
||||
<Settings size={12} className="group-hover:rotate-90 transition-transform duration-300" />
|
||||
Manage Templates
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
<Link
|
||||
to="/script-builder"
|
||||
className="inline-flex items-center gap-2 bg-gradient-brand text-[#101114] font-semibold rounded-[10px] px-4 py-2 shadow-lg shadow-primary/20 hover:opacity-90 active:scale-[0.97] transition-all"
|
||||
>
|
||||
<Wand2 size={16} />
|
||||
Build a New Script
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Tab bar */}
|
||||
<div className="flex gap-6 border-b border-border">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onTabChange('mine')}
|
||||
className={`pb-2 text-sm font-medium transition-colors ${tabClass('mine')}`}
|
||||
>
|
||||
My Scripts
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onTabChange('team')}
|
||||
className={`pb-2 text-sm font-medium transition-colors ${tabClass('team')}`}
|
||||
>
|
||||
Team Scripts
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Two-column layout */}
|
||||
|
||||
@@ -30,7 +30,7 @@ interface ScriptGeneratorState {
|
||||
|
||||
// Actions
|
||||
loadCategories: () => Promise<void>
|
||||
loadTemplates: () => Promise<void>
|
||||
loadTemplates: (filters?: { mine?: boolean; shared?: boolean }) => Promise<void>
|
||||
selectTemplate: (id: string) => Promise<void>
|
||||
setCategory: (id: string | null) => void
|
||||
setSearch: (query: string) => void
|
||||
@@ -67,14 +67,16 @@ export const useScriptGeneratorStore = create<ScriptGeneratorState>()((set, get)
|
||||
}
|
||||
},
|
||||
|
||||
loadTemplates: async () => {
|
||||
loadTemplates: async (filters) => {
|
||||
set({ isLoadingTemplates: true })
|
||||
try {
|
||||
const { activeCategoryId, categories, searchQuery } = get()
|
||||
const category = categories.find(c => c.id === activeCategoryId)
|
||||
const params: { category_slug?: string; search?: string } = {}
|
||||
const params: { category_slug?: string; search?: string; mine?: boolean; shared?: boolean } = {}
|
||||
if (category) params.category_slug = category.slug
|
||||
if (searchQuery) params.search = searchQuery
|
||||
if (filters?.mine) params.mine = true
|
||||
if (filters?.shared) params.shared = true
|
||||
const templates = await scriptsApi.getTemplates(params)
|
||||
set({ templates, isLoadingTemplates: false })
|
||||
} catch {
|
||||
|
||||
@@ -23,6 +23,7 @@ export interface ScriptTemplateListItem {
|
||||
requires_modules: string[]
|
||||
is_verified: boolean
|
||||
usage_count: number
|
||||
language?: string | null
|
||||
}
|
||||
|
||||
export interface ScriptParameterOption {
|
||||
|
||||
Reference in New Issue
Block a user