Add pre-commit hooks for linting and formatting
Set up pre-commit framework with ruff (backend) and ESLint/Prettier/tsc (frontend) hooks to catch issues locally before CI. Auto-format all frontend files with Prettier to comply with the new check. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -42,31 +42,36 @@ interface EncounterModalProps {
|
||||
isPending: boolean
|
||||
}
|
||||
|
||||
const statusOptions: { value: EncounterStatus; label: string; color: string }[] =
|
||||
[
|
||||
{
|
||||
value: 'caught',
|
||||
label: 'Caught',
|
||||
color:
|
||||
'bg-green-100 text-green-800 border-green-300 dark:bg-green-900/40 dark:text-green-300 dark:border-green-700',
|
||||
},
|
||||
{
|
||||
value: 'fainted',
|
||||
label: 'Fainted',
|
||||
color:
|
||||
'bg-red-100 text-red-800 border-red-300 dark:bg-red-900/40 dark:text-red-300 dark:border-red-700',
|
||||
},
|
||||
{
|
||||
value: 'missed',
|
||||
label: 'Missed / Ran',
|
||||
color:
|
||||
'bg-gray-100 text-gray-800 border-gray-300 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-600',
|
||||
},
|
||||
]
|
||||
const statusOptions: {
|
||||
value: EncounterStatus
|
||||
label: string
|
||||
color: string
|
||||
}[] = [
|
||||
{
|
||||
value: 'caught',
|
||||
label: 'Caught',
|
||||
color:
|
||||
'bg-green-100 text-green-800 border-green-300 dark:bg-green-900/40 dark:text-green-300 dark:border-green-700',
|
||||
},
|
||||
{
|
||||
value: 'fainted',
|
||||
label: 'Fainted',
|
||||
color:
|
||||
'bg-red-100 text-red-800 border-red-300 dark:bg-red-900/40 dark:text-red-300 dark:border-red-700',
|
||||
},
|
||||
{
|
||||
value: 'missed',
|
||||
label: 'Missed / Ran',
|
||||
color:
|
||||
'bg-gray-100 text-gray-800 border-gray-300 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-600',
|
||||
},
|
||||
]
|
||||
|
||||
const SPECIAL_METHODS = ['starter', 'gift', 'fossil', 'trade']
|
||||
|
||||
function groupByMethod(pokemon: RouteEncounterDetail[]): { method: string; pokemon: RouteEncounterDetail[] }[] {
|
||||
function groupByMethod(
|
||||
pokemon: RouteEncounterDetail[]
|
||||
): { method: string; pokemon: RouteEncounterDetail[] }[] {
|
||||
const groups = new Map<string, RouteEncounterDetail[]>()
|
||||
for (const rp of pokemon) {
|
||||
const list = groups.get(rp.encounterMethod) ?? []
|
||||
@@ -84,7 +89,7 @@ function groupByMethod(pokemon: RouteEncounterDetail[]): { method: string; pokem
|
||||
|
||||
function pickRandomPokemon(
|
||||
pokemon: RouteEncounterDetail[],
|
||||
dupedIds?: Set<number>,
|
||||
dupedIds?: Set<number>
|
||||
): RouteEncounterDetail | null {
|
||||
const eligible = dupedIds
|
||||
? pokemon.filter((rp) => !dupedIds.has(rp.pokemonId))
|
||||
@@ -109,17 +114,17 @@ export function EncounterModal({
|
||||
}: EncounterModalProps) {
|
||||
const { data: routePokemon, isLoading: loadingPokemon } = useRoutePokemon(
|
||||
route.id,
|
||||
gameId,
|
||||
gameId
|
||||
)
|
||||
|
||||
const [selectedPokemon, setSelectedPokemon] =
|
||||
useState<RouteEncounterDetail | null>(null)
|
||||
const [status, setStatus] = useState<EncounterStatus>(
|
||||
existing?.status ?? 'caught',
|
||||
existing?.status ?? 'caught'
|
||||
)
|
||||
const [nickname, setNickname] = useState(existing?.nickname ?? '')
|
||||
const [catchLevel, setCatchLevel] = useState<string>(
|
||||
existing?.catchLevel?.toString() ?? '',
|
||||
existing?.catchLevel?.toString() ?? ''
|
||||
)
|
||||
const [faintLevel, setFaintLevel] = useState<string>('')
|
||||
const [deathCause, setDeathCause] = useState('')
|
||||
@@ -128,27 +133,31 @@ export function EncounterModal({
|
||||
const isEditing = !!existing
|
||||
|
||||
const showSuggestions = !!namingScheme && status === 'caught' && !isEditing
|
||||
const lineagePokemonId = isGenlocke && selectedPokemon ? selectedPokemon.pokemonId : null
|
||||
const { data: suggestions, refetch: regenerate, isFetching: loadingSuggestions } =
|
||||
useNameSuggestions(showSuggestions ? runId : null, lineagePokemonId)
|
||||
const lineagePokemonId =
|
||||
isGenlocke && selectedPokemon ? selectedPokemon.pokemonId : null
|
||||
const {
|
||||
data: suggestions,
|
||||
refetch: regenerate,
|
||||
isFetching: loadingSuggestions,
|
||||
} = useNameSuggestions(showSuggestions ? runId : null, lineagePokemonId)
|
||||
|
||||
// Pre-select pokemon when editing
|
||||
useEffect(() => {
|
||||
if (existing && routePokemon) {
|
||||
const match = routePokemon.find(
|
||||
(rp) => rp.pokemonId === existing.pokemonId,
|
||||
(rp) => rp.pokemonId === existing.pokemonId
|
||||
)
|
||||
if (match) setSelectedPokemon(match)
|
||||
}
|
||||
}, [existing, routePokemon])
|
||||
|
||||
const filteredPokemon = routePokemon?.filter((rp) =>
|
||||
rp.pokemon.name.toLowerCase().includes(search.toLowerCase()),
|
||||
rp.pokemon.name.toLowerCase().includes(search.toLowerCase())
|
||||
)
|
||||
|
||||
const groupedPokemon = useMemo(
|
||||
() => (filteredPokemon ? groupByMethod(filteredPokemon) : []),
|
||||
[filteredPokemon],
|
||||
[filteredPokemon]
|
||||
)
|
||||
const hasMultipleGroups = groupedPokemon.length > 1
|
||||
|
||||
@@ -224,13 +233,15 @@ export function EncounterModal({
|
||||
loadingPokemon ||
|
||||
!routePokemon ||
|
||||
(dupedPokemonIds
|
||||
? routePokemon.every((rp) => dupedPokemonIds.has(rp.pokemonId))
|
||||
? routePokemon.every((rp) =>
|
||||
dupedPokemonIds.has(rp.pokemonId)
|
||||
)
|
||||
: false)
|
||||
}
|
||||
onClick={() => {
|
||||
if (routePokemon) {
|
||||
setSelectedPokemon(
|
||||
pickRandomPokemon(routePokemon, dupedPokemonIds),
|
||||
pickRandomPokemon(routePokemon, dupedPokemonIds)
|
||||
)
|
||||
}
|
||||
}}
|
||||
@@ -268,12 +279,15 @@ export function EncounterModal({
|
||||
)}
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{pokemon.map((rp) => {
|
||||
const isDuped = dupedPokemonIds?.has(rp.pokemonId) ?? false
|
||||
const isDuped =
|
||||
dupedPokemonIds?.has(rp.pokemonId) ?? false
|
||||
return (
|
||||
<button
|
||||
key={rp.id}
|
||||
type="button"
|
||||
onClick={() => !isDuped && setSelectedPokemon(rp)}
|
||||
onClick={() =>
|
||||
!isDuped && setSelectedPokemon(rp)
|
||||
}
|
||||
disabled={isDuped}
|
||||
className={`flex flex-col items-center p-2 rounded-lg border text-center transition-colors ${
|
||||
isDuped
|
||||
@@ -299,16 +313,24 @@ export function EncounterModal({
|
||||
</span>
|
||||
{isDuped && (
|
||||
<span className="text-[10px] text-gray-400 italic">
|
||||
{retiredPokemonIds?.has(rp.pokemonId) ? 'retired (HoF)' : 'already caught'}
|
||||
{retiredPokemonIds?.has(rp.pokemonId)
|
||||
? 'retired (HoF)'
|
||||
: 'already caught'}
|
||||
</span>
|
||||
)}
|
||||
{!isDuped && SPECIAL_METHODS.includes(rp.encounterMethod) && (
|
||||
<EncounterMethodBadge method={rp.encounterMethod} />
|
||||
)}
|
||||
{!isDuped &&
|
||||
SPECIAL_METHODS.includes(
|
||||
rp.encounterMethod
|
||||
) && (
|
||||
<EncounterMethodBadge
|
||||
method={rp.encounterMethod}
|
||||
/>
|
||||
)}
|
||||
{!isDuped && (
|
||||
<span className="text-[10px] text-gray-400">
|
||||
Lv. {rp.minLevel}
|
||||
{rp.maxLevel !== rp.minLevel && `–${rp.maxLevel}`}
|
||||
{rp.maxLevel !== rp.minLevel &&
|
||||
`–${rp.maxLevel}`}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
@@ -518,11 +540,7 @@ export function EncounterModal({
|
||||
onClick={handleSubmit}
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded-lg font-medium hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
>
|
||||
{isPending
|
||||
? 'Saving...'
|
||||
: isEditing
|
||||
? 'Update'
|
||||
: 'Log Encounter'}
|
||||
{isPending ? 'Saving...' : isEditing ? 'Update' : 'Log Encounter'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user