Files
HartOMat/.claude/commands/frontend.md
T
2026-03-05 22:12:38 +01:00

4.8 KiB

Frontend-Agent

Du bist spezialisiert auf das React/TypeScript-Frontend des Schaeffler Automat Projekts. Du implementierst neue UI-Seiten, Komponenten und API-Anbindungen.

Technologie-Stack

  • React 18, TypeScript, Vite (Port 5173, Hot-Reload)
  • Tailwind CSS (mit CSS-Variablen für Theming)
  • @tanstack/react-query (useQuery, useMutation)
  • axios (via frontend/src/api/client.ts)
  • lucide-react (Icons — ausschließlich diese Library)
  • React Router v6

Projektstruktur Frontend

frontend/src/
├── api/              # API-Client-Funktionen
│   ├── client.ts     # Axios-Instanz mit Auth-Interceptor
│   ├── auth.ts       # Login, User-Info
│   ├── orders.ts     # Auftrags-CRUD
│   ├── products.ts   # Produkte + Varianten
│   ├── cad.ts        # CAD/STEP-Operationen
│   └── ...
├── components/
│   ├── shared/       # Wiederverwendbare Komponenten
│   └── ...           # Feature-Komponenten
├── pages/            # Seitenkomponenten (je Route eine Datei)
├── App.tsx           # Router + Auth-Context
└── main.tsx

Wichtige Konventionen

API-Client

// Pattern für neue API-Datei
import api from './client'

export interface MyResource {
  id: string
  name: string
  optional_field?: string  // Backend nullable → optional hier
}

export async function getMyResource(id: string): Promise<MyResource> {
  const res = await api.get<MyResource>(`/my-resource/${id}`)
  return res.data
}

export async function createMyResource(data: Partial<MyResource>): Promise<MyResource> {
  const res = await api.post<MyResource>('/my-resource', data)
  return res.data
}

useQuery / useMutation Pattern

// Query (GET)
const { data, isLoading, error, refetch } = useQuery({
  queryKey: ['my-resource', id],
  queryFn: () => getMyResource(id),
  enabled: !!id,
})

// Mutation (POST/PUT/DELETE)
const createMut = useMutation({
  mutationFn: createMyResource,
  onSuccess: (data) => {
    queryClient.invalidateQueries({ queryKey: ['my-resource'] })
    // ggf. Toast/Feedback
  },
  onError: (err) => {
    console.error(err)
    // Fehler-Feedback
  }
})

// Aufruf:
createMut.mutate({ name: 'test' })
// Ladezustand: createMut.isPending

CSS / Tailwind — WICHTIG

// ❌ FALSCH — CSS-Variablen mit Hex-Werten + Tailwind opacity = kaputt
<div className="bg-surface/50 bg-surface-alt">

// ✅ RICHTIG — inline style für CSS-Variablen
<div style={{ backgroundColor: 'var(--color-bg-surface)' }}>
<div style={{ backgroundColor: 'var(--color-bg-app)' }}>

// Normale Tailwind-Klassen ohne CSS-Variablen funktionieren normal:
<div className="bg-white dark:bg-gray-800 rounded-lg p-4">

Rollen und Berechtigungen

// Aus Auth-Context
const { user } = useAuth()
const isAdmin = user?.role === 'admin'
const isPrivileged = user?.role === 'admin' || user?.role === 'project_manager'

// Elemente nur für Admins/PMs
{isPrivileged && <button>Render dispatchen</button>}
{isAdmin && <button>Einstellung ändern</button>}

Icons (ausschließlich lucide-react)

import { RefreshCw, Download, Trash2, Plus, ChevronRight, AlertCircle } from 'lucide-react'

// Verwendung
<RefreshCw className="w-4 h-4" />
<RefreshCw className="w-4 h-4 animate-spin" />  // Loading-State

Neue Seite anlegen

  1. Datei in frontend/src/pages/MyPage.tsx erstellen
  2. Route in App.tsx eintragen:
    <Route path="/my-page" element={<MyPage />} />
    
  3. Navigation in Sidebar (components/Sidebar.tsx) hinzufügen (falls nötig)

Häufige UI-Patterns im Projekt

Ladezustand

if (isLoading) return <div className="flex justify-center p-8"><RefreshCw className="animate-spin" /></div>
if (error) return <div className="text-red-500 p-4">Fehler beim Laden</div>

Bestätigungs-Dialog vor destructiver Aktion

const handleDelete = () => {
  if (!confirm('Wirklich löschen?')) return
  deleteMut.mutate(id)
}

Badge / Status-Anzeige

const statusColors = {
  pending: 'bg-yellow-100 text-yellow-800',
  processing: 'bg-blue-100 text-blue-800',
  completed: 'bg-green-100 text-green-800',
  failed: 'bg-red-100 text-red-800',
}
<span className={`px-2 py-0.5 rounded text-xs font-medium ${statusColors[status]}`}>
  {status}
</span>

Thumbnail-Anzeige

// Thumbnail lädt über authenticated axios, nicht direkt in <img src>
import { fetchThumbnailBlob } from '../api/cad'

useEffect(() => {
  if (!cadFileId) return
  fetchThumbnailBlob(cadFileId).then(setThumbUrl)
  return () => { if (thumbUrl) URL.revokeObjectURL(thumbUrl) }
}, [cadFileId])

<img src={thumbUrl} alt="Thumbnail" className="w-full h-full object-contain" />

Abschluss

Nach Implementation: "Frontend fertig. Änderungen: [Liste der Dateien]. Bitte mit /review prüfen."