Files
food-tracer/client/src/components/CalorieLookup.tsx
T

112 lines
4.9 KiB
TypeScript

import { useState } from "react";
import { Button, Spinner } from "react-bootstrap";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faMagnifyingGlass, faArrowUpRightFromSquare, faBookmark } from "@fortawesome/free-solid-svg-icons";
import { CalorieSearchResult, searchCalories } from "../../../types";
type Props = {
/** Název jídla, ke kterému se energie hledá. */
name: string,
/** Zavolá se po výběru návrhu s energií na 100 g v kcal. */
onPick: (caloriesPer100g: number) => void,
};
/** Návrhy pocházející z vlastní knihovny se odlišují ikonou a popiskem. */
const LIBRARY_ORIGIN = 'library';
/**
* Vyhledání energetické hodnoty k názvu jídla.
*
* Návrhy chodí z vlastní knihovny dřívějších zadání a od externího poskytovatele.
* Když je poskytovatel nedostupný (což se stává), zůstane aspoň knihovna a odkaz
* na KalorickéTabulky.cz, odkud si hodnotu uživatel opíše ručně — automaticky se
* odtud nic nestahuje, veřejné API nemají.
*/
export default function CalorieLookup({ name, onPick }: Readonly<Props>) {
const [result, setResult] = useState<CalorieSearchResult | undefined>();
const [searching, setSearching] = useState(false);
const [copied, setCopied] = useState(false);
const canSearch = name.trim().length > 0 && !searching;
/**
* Zkopíruje název jídla do schránky při otevření Kalorických tabulek.
* Jejich vyhledávání nejde předvyplnit z URL, takže aspoň stačí vložit.
*/
const copyName = () => {
navigator.clipboard?.writeText(name.trim())
.then(() => setCopied(true))
.catch(() => { /* schránka není dostupná, odkaz se stejně otevře */ });
};
const search = async () => {
if (!canSearch) return;
setSearching(true);
const response = await searchCalories({ query: { q: name.trim() } });
setResult(response.data);
setCopied(false);
setSearching(false);
};
return (
<div className="calorie-lookup">
<Button variant="outline-secondary" size="sm" onClick={search} disabled={!canSearch}>
{searching
? <Spinner animation="border" size="sm" />
: <FontAwesomeIcon icon={faMagnifyingGlass} />}
{' '}Najít kalorie
</Button>
{!canSearch && !name.trim().length && (
<span className="calorie-lookup-hint">nejdřív vyplňte název jídla</span>
)}
{result && !searching && (
<div className="calorie-lookup-results">
{result.suggestions.map(suggestion => (
<button
type="button"
className="calorie-suggestion"
key={`${suggestion.origin}-${suggestion.name}-${suggestion.caloriesPer100g}`}
onClick={() => onPick(suggestion.caloriesPer100g)}
title="Použít tuto hodnotu"
>
{suggestion.origin === LIBRARY_ORIGIN && (
<FontAwesomeIcon icon={faBookmark} className="calorie-suggestion-icon" />
)}
<span className="calorie-suggestion-name">{suggestion.name}</span>
{suggestion.brand && (
<span className="calorie-suggestion-brand">{suggestion.brand}</span>
)}
<span className="calorie-suggestion-value">{suggestion.caloriesPer100g} kcal/100 g</span>
</button>
))}
{!result.suggestions.length && (
<div className="calorie-lookup-hint">
Nic se nenašlo{!result.providerAvailable && ' a externí databáze je nedostupná'}.
</div>
)}
{result.providerAvailable === false && !!result.suggestions.length && (
<div className="calorie-lookup-hint">
Externí databáze je nedostupná, návrhy jsou jen z vašich dřívějších zadání.
</div>
)}
<a
className="calorie-lookup-external"
href={result.externalSearchUrl}
target="_blank"
rel="noreferrer noopener"
onClick={copyName}
>
{copied ? 'Název zkopírován, vložte ho do jejich hledání' : 'Otevřít KalorickéTabulky.cz'}
{' '}<FontAwesomeIcon icon={faArrowUpRightFromSquare} />
</a>
</div>
)}
</div>
);
}