diff --git a/frontend/src/api/scripts.ts b/frontend/src/api/scripts.ts index 07961cf8..6ff4bfdd 100644 --- a/frontend/src/api/scripts.ts +++ b/frontend/src/api/scripts.ts @@ -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 { const response = await apiClient.get('/scripts/templates', { params }) return response.data diff --git a/frontend/src/pages/ScriptLibraryPage.tsx b/frontend/src/pages/ScriptLibraryPage.tsx index 796a5d28..13903a2d 100644 --- a/frontend/src/pages/ScriptLibraryPage.tsx +++ b/frontend/src/pages/ScriptLibraryPage.tsx @@ -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('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 (
{/* Page header */} -
-

Script Library

-

- Browse PowerShell templates, fill in parameters, and generate ready-to-run scripts. -

- {isEngineer && ( - - - Manage Templates - - )} +
+
+

Script Library

+

+ Browse PowerShell templates, fill in parameters, and generate ready-to-run scripts. +

+ {isEngineer && ( + + + Manage Templates + + )} +
+ + + Build a New Script + +
+ + {/* Tab bar */} +
+ +
{/* Two-column layout */} diff --git a/frontend/src/store/scriptGeneratorStore.ts b/frontend/src/store/scriptGeneratorStore.ts index 66968119..4cee1aa5 100644 --- a/frontend/src/store/scriptGeneratorStore.ts +++ b/frontend/src/store/scriptGeneratorStore.ts @@ -30,7 +30,7 @@ interface ScriptGeneratorState { // Actions loadCategories: () => Promise - loadTemplates: () => Promise + loadTemplates: (filters?: { mine?: boolean; shared?: boolean }) => Promise selectTemplate: (id: string) => Promise setCategory: (id: string | null) => void setSearch: (query: string) => void @@ -67,14 +67,16 @@ export const useScriptGeneratorStore = create()((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 { diff --git a/frontend/src/types/scripts.ts b/frontend/src/types/scripts.ts index 92678126..57af02ab 100644 --- a/frontend/src/types/scripts.ts +++ b/frontend/src/types/scripts.ts @@ -23,6 +23,7 @@ export interface ScriptTemplateListItem { requires_modules: string[] is_verified: boolean usage_count: number + language?: string | null } export interface ScriptParameterOption {