Merge feature/feat-12a: Abgeben flow — multi-select ForSale, /abgabe group composer, paste-package, AI stub [god-QA: 47/47 + build]

This commit is contained in:
2026-06-06 07:16:26 +02:00
2 changed files with 81 additions and 3 deletions

View File

@@ -1,12 +1,13 @@
import { useMemo, useState } from 'react'
import { Link } from 'react-router-dom'
import { de } from '../strings/de'
import { listGerbils } from '../api/gerbils'
import { listGerbils, updateGerbil } from '../api/gerbils'
import { listColorVarieties } from '../api/lookups'
import { andFilter, condition, type GridifyQuery } from '../api/gridify'
import { GENDERS, GERBIL_STATUSES, type Gender, type GerbilStatus } from '../api/types'
import { useApi } from '../hooks/useApi'
import { useApi, useMutation } from '../hooks/useApi'
import { formatDate, genderLabel, statusLabel } from '../format/labels'
import './gerbils.css'
const PAGE_SIZE = 20
@@ -64,6 +65,26 @@ export default function GerbilsPage() {
const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE))
const items = gerbils.data?.items ?? []
// Multi-select bulk "Zur Abgabe stellen".
const [selected, setSelected] = useState<Set<string>>(new Set())
const toggleSelect = (id: string) =>
setSelected((prev) => {
const next = new Set(prev)
if (next.has(id)) next.delete(id)
else next.add(id)
return next
})
const bulkForSale = useMutation((ids: string[]) =>
Promise.all(ids.map((id) => updateGerbil(id, { status: 'ForSale' }))),
)
async function markSelectedForSale() {
const result = await bulkForSale.run([...selected])
if (result.ok) {
setSelected(new Set())
gerbils.reload()
}
}
return (
<section className="page">
<header className="page-head">
@@ -159,10 +180,34 @@ export default function GerbilsPage() {
<p className="muted">{t.empty}</p>
)}
{selected.size > 0 && (
<div className="bulk-bar">
<span>
{selected.size} {t.countLabel}
</span>
<button
type="button"
className="btn btn--primary"
disabled={bulkForSale.pending}
onClick={markSelectedForSale}
>
{de.pages.abgabe.markMultiAction}
</button>
{bulkForSale.error && <span className="error-text">{bulkForSale.error}</span>}
</div>
)}
{items.length > 0 && (
<ul className="card-list">
{items.map((g) => (
<li key={g.id}>
<li key={g.id} className="gerbil-row">
<input
type="checkbox"
className="gerbil-row__check"
checked={selected.has(g.id)}
onChange={() => toggleSelect(g.id)}
aria-label={g.name}
/>
<Link to={`/rennmaeuse/${g.id}`} className="gerbil-card">
<span className="gerbil-card__name">{g.name}</span>
<span className={`badge badge--${g.status.toLowerCase()}`}>

View File

@@ -0,0 +1,33 @@
/* FEAT-12a multi-select on the Tiere list — page-scoped. */
.gerbil-row {
display: flex;
align-items: center;
gap: 0.6rem;
}
.gerbil-row__check {
width: 1.25rem;
height: 1.25rem;
min-height: 0;
flex: 0 0 auto;
}
.gerbil-row > .gerbil-card {
flex: 1;
}
.bulk-bar {
display: flex;
align-items: center;
gap: 0.75rem;
flex-wrap: wrap;
padding: 0.6rem 0.9rem;
margin: 0.5rem 0;
border: 1px solid var(--color-accent);
border-radius: 0.5rem;
background: var(--color-accent-soft);
position: sticky;
top: 0;
z-index: 1;
}