Compare commits
2 Commits
aa79f9dbe5
...
c9723d2fc8
| Author | SHA1 | Date | |
|---|---|---|---|
| c9723d2fc8 | |||
| 07dd5c9b8e |
@@ -1,12 +1,13 @@
|
|||||||
import { useMemo, useState } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { de } from '../strings/de'
|
import { de } from '../strings/de'
|
||||||
import { listGerbils } from '../api/gerbils'
|
import { listGerbils, updateGerbil } from '../api/gerbils'
|
||||||
import { listColorVarieties } from '../api/lookups'
|
import { listColorVarieties } from '../api/lookups'
|
||||||
import { andFilter, condition, type GridifyQuery } from '../api/gridify'
|
import { andFilter, condition, type GridifyQuery } from '../api/gridify'
|
||||||
import { GENDERS, GERBIL_STATUSES, type Gender, type GerbilStatus } from '../api/types'
|
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 { formatDate, genderLabel, statusLabel } from '../format/labels'
|
||||||
|
import './gerbils.css'
|
||||||
|
|
||||||
const PAGE_SIZE = 20
|
const PAGE_SIZE = 20
|
||||||
|
|
||||||
@@ -64,6 +65,26 @@ export default function GerbilsPage() {
|
|||||||
const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE))
|
const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE))
|
||||||
const items = gerbils.data?.items ?? []
|
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 (
|
return (
|
||||||
<section className="page">
|
<section className="page">
|
||||||
<header className="page-head">
|
<header className="page-head">
|
||||||
@@ -159,10 +180,34 @@ export default function GerbilsPage() {
|
|||||||
<p className="muted">{t.empty}</p>
|
<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 && (
|
{items.length > 0 && (
|
||||||
<ul className="card-list">
|
<ul className="card-list">
|
||||||
{items.map((g) => (
|
{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">
|
<Link to={`/rennmaeuse/${g.id}`} className="gerbil-card">
|
||||||
<span className="gerbil-card__name">{g.name}</span>
|
<span className="gerbil-card__name">{g.name}</span>
|
||||||
<span className={`badge badge--${g.status.toLowerCase()}`}>
|
<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