fix: resolve all 15 frontend ESLint errors for green CI
- Replace setState-in-effect with state-based tracking (AdminLayout, EditCategoryModal) - Convert inline SortIcon component to getSortIcon function (TreeTableView) - Remove unused catch parameters (CreateCategoryModal, EditCategoryModal) - Replace `any` types with proper types (SessionFilters, AdminCategoriesPage, SessionHistoryPage) - Fix unused destructuring variable (StepRatingModal) - Fix constant binary expression in test (utils.test.ts) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,12 +5,16 @@ import { AdminSidebar } from './AdminSidebar'
|
|||||||
|
|
||||||
export function AdminLayout() {
|
export function AdminLayout() {
|
||||||
const [mobileOpen, setMobileOpen] = useState(false)
|
const [mobileOpen, setMobileOpen] = useState(false)
|
||||||
|
const [prevPathname, setPrevPathname] = useState('')
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
|
||||||
// Close on route change
|
// Close on route change (state-based tracking, no effect needed)
|
||||||
useEffect(() => {
|
if (prevPathname !== location.pathname) {
|
||||||
setMobileOpen(false)
|
setPrevPathname(location.pathname)
|
||||||
}, [location.pathname])
|
if (mobileOpen) {
|
||||||
|
setMobileOpen(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleKeyDown = useCallback((e: KeyboardEvent) => {
|
const handleKeyDown = useCallback((e: KeyboardEvent) => {
|
||||||
if (e.key === 'Escape') setMobileOpen(false)
|
if (e.key === 'Escape') setMobileOpen(false)
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export function CreateCategoryModal({
|
|||||||
// Reset form on success
|
// Reset form on success
|
||||||
setName('')
|
setName('')
|
||||||
setDescription('')
|
setDescription('')
|
||||||
} catch (err) {
|
} catch {
|
||||||
setError('Failed to create category')
|
setError('Failed to create category')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState } from 'react'
|
||||||
import { X } from 'lucide-react'
|
import { X } from 'lucide-react'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import type { StepCategoryListItem } from '@/types'
|
import type { StepCategoryListItem } from '@/types'
|
||||||
@@ -21,14 +21,17 @@ export function EditCategoryModal({
|
|||||||
const [name, setName] = useState('')
|
const [name, setName] = useState('')
|
||||||
const [description, setDescription] = useState('')
|
const [description, setDescription] = useState('')
|
||||||
const [error, setError] = useState('')
|
const [error, setError] = useState('')
|
||||||
|
const [prevCategoryId, setPrevCategoryId] = useState<string | null>(null)
|
||||||
|
|
||||||
// Pre-populate form when category changes
|
// Pre-populate form when category changes (state-based tracking)
|
||||||
useEffect(() => {
|
if (category && category.id !== prevCategoryId) {
|
||||||
if (category) {
|
setPrevCategoryId(category.id)
|
||||||
setName(category.name)
|
setName(category.name)
|
||||||
setDescription(category.description || '')
|
setDescription(category.description || '')
|
||||||
}
|
}
|
||||||
}, [category])
|
if (!category && prevCategoryId !== null) {
|
||||||
|
setPrevCategoryId(null)
|
||||||
|
}
|
||||||
|
|
||||||
if (!isOpen || !category) return null
|
if (!isOpen || !category) return null
|
||||||
|
|
||||||
@@ -51,7 +54,7 @@ export function EditCategoryModal({
|
|||||||
name: name.trim(),
|
name: name.trim(),
|
||||||
description: description.trim()
|
description: description.trim()
|
||||||
})
|
})
|
||||||
} catch (err) {
|
} catch {
|
||||||
setError('Failed to update category')
|
setError('Failed to update category')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export function TreeTableView({
|
|||||||
onSortChange?.(apiSort)
|
onSortChange?.(apiSort)
|
||||||
}
|
}
|
||||||
|
|
||||||
const SortIcon = ({ column }: { column: SortColumn }) => {
|
const getSortIcon = (column: SortColumn) => {
|
||||||
if (sortColumn !== column) return null
|
if (sortColumn !== column) return null
|
||||||
return sortDirection === 'asc' ? (
|
return sortDirection === 'asc' ? (
|
||||||
<ChevronUp className="h-3.5 w-3.5" />
|
<ChevronUp className="h-3.5 w-3.5" />
|
||||||
@@ -79,7 +79,7 @@ export function TreeTableView({
|
|||||||
>
|
>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
Name
|
Name
|
||||||
<SortIcon column="name" />
|
{getSortIcon('name')}
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th className="hidden md:table-cell px-4 py-3 text-left text-sm font-medium text-muted-foreground">
|
<th className="hidden md:table-cell px-4 py-3 text-left text-sm font-medium text-muted-foreground">
|
||||||
@@ -91,7 +91,7 @@ export function TreeTableView({
|
|||||||
>
|
>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
Category
|
Category
|
||||||
<SortIcon column="category" />
|
{getSortIcon('category')}
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th className="hidden xl:table-cell px-4 py-3 text-left text-sm font-medium text-muted-foreground">
|
<th className="hidden xl:table-cell px-4 py-3 text-left text-sm font-medium text-muted-foreground">
|
||||||
@@ -103,7 +103,7 @@ export function TreeTableView({
|
|||||||
>
|
>
|
||||||
<div className="flex items-center justify-center gap-1">
|
<div className="flex items-center justify-center gap-1">
|
||||||
Ver.
|
Ver.
|
||||||
<SortIcon column="version" />
|
{getSortIcon('version')}
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
@@ -112,7 +112,7 @@ export function TreeTableView({
|
|||||||
>
|
>
|
||||||
<div className="flex items-center justify-center gap-1">
|
<div className="flex items-center justify-center gap-1">
|
||||||
Uses
|
Uses
|
||||||
<SortIcon column="usage" />
|
{getSortIcon('usage')}
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
@@ -121,7 +121,7 @@ export function TreeTableView({
|
|||||||
>
|
>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
Updated
|
Updated
|
||||||
<SortIcon column="updated" />
|
{getSortIcon('updated')}
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-4 py-3 text-right text-sm font-medium text-muted-foreground">
|
<th className="px-4 py-3 text-right text-sm font-medium text-muted-foreground">
|
||||||
|
|||||||
@@ -33,11 +33,12 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
|
|||||||
const [showDatePicker, setShowDatePicker] = useState(false)
|
const [showDatePicker, setShowDatePicker] = useState(false)
|
||||||
const [localDateRange, setLocalDateRange] = useState<DateRange | undefined>(filters.dateRange)
|
const [localDateRange, setLocalDateRange] = useState<DateRange | undefined>(filters.dateRange)
|
||||||
|
|
||||||
|
const filtersDateRange = filters.dateRange
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLocalDateRange(filters.dateRange)
|
setLocalDateRange(filtersDateRange)
|
||||||
}, [filters.dateRange])
|
}, [filtersDateRange])
|
||||||
|
|
||||||
const handleFilterChange = (key: keyof SessionFilterState, value: any) => {
|
const handleFilterChange = (key: keyof SessionFilterState, value: SessionFilterState[keyof SessionFilterState]) => {
|
||||||
onChange({ ...filters, [key]: value })
|
onChange({ ...filters, [key]: value })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export function StepRatingModal({
|
|||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
// Filter out steps with no rating
|
// Filter out steps with no rating
|
||||||
const ratingsToSubmit = new Map(
|
const ratingsToSubmit = new Map(
|
||||||
Array.from(ratings.entries()).filter(([_, data]) => data.rating > 0)
|
Array.from(ratings.entries()).filter(([, data]) => data.rating > 0)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (ratingsToSubmit.size === 0) {
|
if (ratingsToSubmit.size === 0) {
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ describe('cn', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('handles conditional classes', () => {
|
it('handles conditional classes', () => {
|
||||||
expect(cn('base', false && 'hidden', 'visible')).toBe('base visible')
|
const isHidden = false
|
||||||
|
expect(cn('base', isHidden && 'hidden', 'visible')).toBe('base visible')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('deduplicates tailwind classes', () => {
|
it('deduplicates tailwind classes', () => {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { toast } from '@/lib/toast'
|
|||||||
|
|
||||||
export function AdminCategoriesPage() {
|
export function AdminCategoriesPage() {
|
||||||
const [categories, setCategories] = useState<StepCategoryListItem[]>([])
|
const [categories, setCategories] = useState<StepCategoryListItem[]>([])
|
||||||
const [allSteps, setAllSteps] = useState<any[]>([])
|
const [allSteps, setAllSteps] = useState<{ category_id?: string }[]>([])
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
const [showCreateModal, setShowCreateModal] = useState(false)
|
const [showCreateModal, setShowCreateModal] = useState(false)
|
||||||
const [showEditModal, setShowEditModal] = useState(false)
|
const [showEditModal, setShowEditModal] = useState(false)
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ export function SessionHistoryPage() {
|
|||||||
const loadSessions = async () => {
|
const loadSessions = async () => {
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
try {
|
try {
|
||||||
const params: any = {}
|
const params: Record<string, string | boolean> = {}
|
||||||
|
|
||||||
// Tab filter (all/active/completed)
|
// Tab filter (all/active/completed)
|
||||||
if (filter !== 'all') {
|
if (filter !== 'all') {
|
||||||
|
|||||||
Reference in New Issue
Block a user