Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46dd8df55c |
@@ -112,8 +112,8 @@ The prebuilt image is at `git.draic.info/nils/photos` (single image contains
|
|||||||
`server`, `worker`, and the built frontend). To build your own instead:
|
`server`, `worker`, and the built frontend). To build your own instead:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
docker build -t registry.example.com/you/photos:0.4.2 .
|
docker build -t registry.example.com/you/photos:0.4.3 .
|
||||||
docker push registry.example.com/you/photos:0.4.2
|
docker push registry.example.com/you/photos:0.4.3
|
||||||
```
|
```
|
||||||
|
|
||||||
Install the chart, pointing it at your existing Postgres and S3:
|
Install the chart, pointing it at your existing Postgres and S3:
|
||||||
@@ -121,7 +121,7 @@ Install the chart, pointing it at your existing Postgres and S3:
|
|||||||
```sh
|
```sh
|
||||||
helm install photos deploy/chart \
|
helm install photos deploy/chart \
|
||||||
--set image.repository=git.draic.info/nils/photos \
|
--set image.repository=git.draic.info/nils/photos \
|
||||||
--set image.tag=0.4.2 \
|
--set image.tag=0.4.3 \
|
||||||
--set publicUrl=https://photos.example.com \
|
--set publicUrl=https://photos.example.com \
|
||||||
--set ingress.host=photos.example.com \
|
--set ingress.host=photos.example.com \
|
||||||
--set config.oidcIssuer=https://auth.example.com \
|
--set config.oidcIssuer=https://auth.example.com \
|
||||||
|
|||||||
@@ -23,9 +23,6 @@ const FEEDBACK_FILTERS = [
|
|||||||
{ key: 'accept', label: '👍' },
|
{ key: 'accept', label: '👍' },
|
||||||
{ key: 'reject', label: '👎' },
|
{ key: 'reject', label: '👎' },
|
||||||
{ key: 'undecided', label: 'Undecided' },
|
{ key: 'undecided', label: 'Undecided' },
|
||||||
{ key: 'star3', label: '★ 3+' },
|
|
||||||
{ key: 'star4', label: '★ 4+' },
|
|
||||||
{ key: 'star5', label: '★ 5' },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
// Expiry convention, in one place for the create form and the row editor:
|
// Expiry convention, in one place for the create form and the row editor:
|
||||||
@@ -432,28 +429,27 @@ export default function AlbumPage() {
|
|||||||
return ratings.reduce((sum, r) => sum + r.rating, 0) / ratings.length
|
return ratings.reduce((sum, r) => sum + r.rating, 0) / ratings.length
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter over client feedback: verdicts, or minimum average star rating.
|
// Two combinable filter dimensions over client feedback: verdict chips
|
||||||
|
// plus a minimum-average-stars threshold (0 = off).
|
||||||
const [filter, setFilter] = useState('all')
|
const [filter, setFilter] = useState('all')
|
||||||
const matchesFilter = (p, key) => {
|
const [minStars, setMinStars] = useState(0)
|
||||||
|
const matchesVerdict = (p, key) => {
|
||||||
if (key === 'all') return true
|
if (key === 'all') return true
|
||||||
const verdicts = feedback[p.id]?.verdicts || []
|
const verdicts = feedback[p.id]?.verdicts || []
|
||||||
if (key === 'accept') return verdicts.some((v) => v.verdict === 'accept')
|
if (key === 'accept') return verdicts.some((v) => v.verdict === 'accept')
|
||||||
if (key === 'reject') return verdicts.some((v) => v.verdict === 'reject')
|
if (key === 'reject') return verdicts.some((v) => v.verdict === 'reject')
|
||||||
if (key === 'undecided') return verdicts.length === 0
|
return verdicts.length === 0
|
||||||
return (avgRating(p.id) ?? 0) >= Number(key.slice(4))
|
|
||||||
}
|
}
|
||||||
const shown = ready.filter((p) => matchesFilter(p, filter))
|
const shown = ready.filter(
|
||||||
|
(p) => matchesVerdict(p, filter) && (minStars === 0 || (avgRating(p.id) ?? 0) >= minStars),
|
||||||
|
)
|
||||||
const filterCounts = useMemo(() => {
|
const filterCounts = useMemo(() => {
|
||||||
const counts = { all: ready.length, accept: 0, reject: 0, undecided: 0, star3: 0, star4: 0, star5: 0 }
|
const counts = { all: ready.length, accept: 0, reject: 0, undecided: 0 }
|
||||||
for (const p of ready) {
|
for (const p of ready) {
|
||||||
const verdicts = feedback[p.id]?.verdicts || []
|
const verdicts = feedback[p.id]?.verdicts || []
|
||||||
if (verdicts.some((v) => v.verdict === 'accept')) counts.accept += 1
|
if (verdicts.some((v) => v.verdict === 'accept')) counts.accept += 1
|
||||||
if (verdicts.some((v) => v.verdict === 'reject')) counts.reject += 1
|
if (verdicts.some((v) => v.verdict === 'reject')) counts.reject += 1
|
||||||
if (verdicts.length === 0) counts.undecided += 1
|
if (verdicts.length === 0) counts.undecided += 1
|
||||||
const avg = avgRating(p.id) ?? 0
|
|
||||||
if (avg >= 3) counts.star3 += 1
|
|
||||||
if (avg >= 4) counts.star4 += 1
|
|
||||||
if (avg >= 5) counts.star5 += 1
|
|
||||||
}
|
}
|
||||||
return counts
|
return counts
|
||||||
}, [detail])
|
}, [detail])
|
||||||
@@ -566,6 +562,13 @@ export default function AlbumPage() {
|
|||||||
{f.label} {filterCounts[f.key]}
|
{f.label} {filterCounts[f.key]}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
|
<span
|
||||||
|
className={`filter-stars${minStars > 0 ? ' active' : ''}`}
|
||||||
|
title="Minimum average rating — click a star to filter, click it again to clear"
|
||||||
|
>
|
||||||
|
<Stars value={minStars} onChange={(n) => setMinStars(n)} small />
|
||||||
|
{minStars > 0 && <span className="muted">≥ {minStars}</span>}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -675,6 +675,22 @@ progress {
|
|||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
margin: 0 0 0.75rem;
|
margin: 0 0 0.75rem;
|
||||||
}
|
}
|
||||||
|
.filter-stars {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.35rem;
|
||||||
|
background: var(--panel);
|
||||||
|
border: 1px solid var(--panel-2);
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 0.1rem 0.7rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
.filter-stars.active {
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
.filter-stars .star {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
/* transient action feedback (keyboard votes that navigate away) */
|
/* transient action feedback (keyboard votes that navigate away) */
|
||||||
.action-flash {
|
.action-flash {
|
||||||
|
|||||||
Reference in New Issue
Block a user