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:
@@ -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()}`}>
|
||||
|
||||
33
gerbil-manager-web/src/pages/gerbils.css
Normal file
33
gerbil-manager-web/src/pages/gerbils.css
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user