Prüfung / Lint, Typen, Tests (push) Canceled after 0s
Prüfung / Images bauen (pdf) (push) Canceled after 0s
Prüfung / Images bauen (web) (push) Canceled after 0s
Prüfung / Images bauen (worker) (push) Canceled after 0s
Images / bauen (pdf, docker/pdf.Dockerfile) (push) Canceled after 0s
Images / bauen (web, docker/web.Dockerfile) (push) Canceled after 0s
Images / bauen (worker, docker/worker.Dockerfile) (push) Canceled after 0s
375 Vorkommen in 210 Dateien. Betrifft Kommentare, Oberflaechentexte und die README; an keiner Stelle Bedeutung, nur Zeichensetzung.
107 lines
3.2 KiB
TypeScript
107 lines
3.2 KiB
TypeScript
'use client'
|
||
|
||
import { useState, useTransition } from 'react'
|
||
import Link from 'next/link'
|
||
import { searchText } from '@/lib/i18n/shell'
|
||
import { commonText } from '@/lib/i18n/common'
|
||
import { deleteViewAction, saveViewAction } from './actions'
|
||
|
||
/**
|
||
* Ansichten: die mitgelieferten und die eigenen.
|
||
*
|
||
* Beide sind dasselbe - eine Suchadresse. Die mitgelieferten stehen im
|
||
* Programm, weil sie von der Gegenwart abhängen („diese Woche"), die eigenen
|
||
* in der Datenbank.
|
||
*/
|
||
export function SavedViews({
|
||
builtins,
|
||
saved,
|
||
currentQuery,
|
||
}: {
|
||
builtins: { key: string; params: string }[]
|
||
saved: { id: string; name: string; query: string }[]
|
||
currentQuery: string
|
||
}) {
|
||
const [name, setName] = useState('')
|
||
const [offen, setOffen] = useState(false)
|
||
const [fehler, setFehler] = useState<string | null>(null)
|
||
const [pending, startTransition] = useTransition()
|
||
|
||
return (
|
||
<div className="mt-4 flex flex-wrap items-center gap-x-4 gap-y-2 text-2xs">
|
||
<span className="font-condensed text-text-dim">{searchText.views}</span>
|
||
|
||
{builtins.map((view) => (
|
||
<Link key={view.key} href={`/suche?${view.params}`} className="font-condensed">
|
||
{searchText.builtin[view.key] ?? view.key}
|
||
</Link>
|
||
))}
|
||
|
||
{saved.map((view) => (
|
||
<span key={view.id} className="inline-flex items-baseline gap-1">
|
||
<Link href={`/suche?${view.query}`} className="font-condensed">
|
||
{view.name}
|
||
</Link>
|
||
<button
|
||
type="button"
|
||
disabled={pending}
|
||
title={searchText.deleteView}
|
||
onClick={() => startTransition(async () => void (await deleteViewAction({ id: view.id })))}
|
||
className="btn btn-sm"
|
||
>
|
||
×
|
||
</button>
|
||
</span>
|
||
))}
|
||
|
||
{offen ? (
|
||
<span className="inline-flex items-center gap-2">
|
||
<input
|
||
value={name}
|
||
onChange={(event) => setName(event.target.value)}
|
||
placeholder={searchText.saveViewName}
|
||
aria-label={searchText.saveViewName}
|
||
maxLength={60}
|
||
className="field"
|
||
/>
|
||
<button
|
||
type="button"
|
||
disabled={pending || name.trim() === ''}
|
||
onClick={() =>
|
||
startTransition(async () => {
|
||
const ergebnis = await saveViewAction({ name, query: currentQuery })
|
||
if (ergebnis.ok) {
|
||
setName('')
|
||
setOffen(false)
|
||
} else {
|
||
setFehler(ergebnis.error)
|
||
}
|
||
})
|
||
}
|
||
className="btn btn-sm btn-primary"
|
||
>
|
||
{commonText.save}
|
||
</button>
|
||
<button
|
||
type="button"
|
||
onClick={() => setOffen(false)}
|
||
className="btn btn-sm"
|
||
>
|
||
{commonText.cancel}
|
||
</button>
|
||
</span>
|
||
) : (
|
||
<button
|
||
type="button"
|
||
onClick={() => setOffen(true)}
|
||
className="ml-auto btn btn-sm"
|
||
>
|
||
{searchText.saveView}
|
||
</button>
|
||
)}
|
||
|
||
{fehler ? <span className="font-condensed text-stamp-4">{fehler}</span> : null}
|
||
</div>
|
||
)
|
||
}
|