157 lines
6.2 KiB
TypeScript
157 lines
6.2 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { ChevronDown, CloudSun, Globe, MapPin } from 'lucide-react';
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuLabel,
|
|
DropdownMenuTrigger,
|
|
} from '@/components/ui/dropdown-menu';
|
|
import { useTempoUi } from '@/components/tempo-brand';
|
|
import { getLanguages } from '@/translation/service';
|
|
import { withPortugueseFirst } from '@/translation/utils';
|
|
import type { TranslationLanguage } from '@/translation/types';
|
|
|
|
type Weather = { temp: number; description: string; humidity?: number };
|
|
|
|
export function TempoWeather() {
|
|
const { language } = useTempoUi();
|
|
const [weather, setWeather] = useState<Weather | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
useEffect(() => {
|
|
const controller = new AbortController();
|
|
const timer = setTimeout(() => controller.abort(), 8000);
|
|
fetch(`https://teste-info.tempo.co.mz/api/weather?lang=${language}`, {
|
|
signal: controller.signal,
|
|
credentials: 'omit',
|
|
})
|
|
.then((response) => (response.ok ? response.json() : null))
|
|
.then((data: { current?: Weather } | null) => {
|
|
if (data?.current && Number.isFinite(data.current.temp))
|
|
setWeather(data.current);
|
|
})
|
|
.catch(() => {
|
|
/* No guessed temperatures when the source is unavailable. */
|
|
})
|
|
.finally(() => {
|
|
clearTimeout(timer);
|
|
setLoading(false);
|
|
});
|
|
return () => {
|
|
clearTimeout(timer);
|
|
controller.abort();
|
|
};
|
|
}, [language]);
|
|
return (
|
|
<DropdownMenu modal={false}>
|
|
<DropdownMenuTrigger
|
|
className="tempo-utility-button"
|
|
aria-label="Temperatura em Maputo"
|
|
>
|
|
<CloudSun
|
|
size={19}
|
|
aria-hidden="true"
|
|
className="text-amber-300"
|
|
/>
|
|
<span>
|
|
{weather
|
|
? `${Math.round(weather.temp)}°C`
|
|
: loading
|
|
? '…'
|
|
: 'Maputo'}
|
|
</span>
|
|
<ChevronDown size={12} aria-hidden="true" />
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent
|
|
align="end"
|
|
sideOffset={12}
|
|
className="tempo-dropdown w-60 p-4"
|
|
>
|
|
<p className="mb-3 flex items-center gap-2 text-xs font-semibold text-slate-500">
|
|
<MapPin size={14} /> Maputo, Moçambique
|
|
</p>
|
|
{weather ? (
|
|
<>
|
|
<p className="text-3xl font-semibold text-slate-950">
|
|
{Math.round(weather.temp)}°C
|
|
</p>
|
|
<p className="mt-1 text-sm text-slate-600">
|
|
{weather.description}
|
|
</p>
|
|
{weather.humidity !== undefined && (
|
|
<p className="mt-3 text-xs text-slate-500">
|
|
Humidade: {weather.humidity}%
|
|
</p>
|
|
)}
|
|
</>
|
|
) : (
|
|
<p className="text-sm text-slate-600">
|
|
{loading
|
|
? 'A consultar a temperatura…'
|
|
: 'Temperatura temporariamente indisponível.'}
|
|
</p>
|
|
)}
|
|
<p className="mt-3 text-[11px] text-slate-400">
|
|
Meteorologia · Tempo
|
|
</p>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
);
|
|
}
|
|
|
|
export function TempoLanguageSelector() {
|
|
const { language, setLanguage, t } = useTempoUi();
|
|
const [languages, setLanguages] = useState<TranslationLanguage[]>([
|
|
{ code: 'pt', name: 'Português' },
|
|
{ code: 'en', name: 'English' },
|
|
{ code: 'fr', name: 'Français' },
|
|
{ code: 'es', name: 'Español' },
|
|
]);
|
|
const [available, setAvailable] = useState(true);
|
|
useEffect(() => {
|
|
const controller = new AbortController();
|
|
getLanguages(controller.signal).then((items) => {
|
|
const portuguese = items.find((item) => item.code === 'pt');
|
|
if (!portuguese) throw new Error('Portuguese model unavailable');
|
|
setLanguages(withPortugueseFirst(items.filter((item) => item.code === 'pt' || !portuguese.targets || portuguese.targets.includes(item.code))));
|
|
}).catch(() => { if (!controller.signal.aborted) setAvailable(false); });
|
|
return () => controller.abort();
|
|
}, []);
|
|
return (
|
|
<DropdownMenu modal={false}>
|
|
<DropdownMenuTrigger
|
|
className="tempo-utility-button"
|
|
aria-label={t('Idioma da navegação')}
|
|
>
|
|
<Globe size={16} aria-hidden="true" />
|
|
<span className="text-xs font-semibold uppercase">
|
|
{language}
|
|
</span>
|
|
<ChevronDown size={12} aria-hidden="true" />
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent
|
|
align="end"
|
|
sideOffset={12}
|
|
className="tempo-dropdown min-w-44"
|
|
>
|
|
<DropdownMenuLabel className="text-xs text-slate-500">
|
|
{t('Navegação institucional')}
|
|
</DropdownMenuLabel>
|
|
{!available && <DropdownMenuLabel className="text-xs text-slate-500">Tradução temporariamente indisponível</DropdownMenuLabel>}
|
|
{languages.map(({ code, name }) => (
|
|
<DropdownMenuItem
|
|
key={code}
|
|
onSelect={() => setLanguage(code)}
|
|
className={`tempo-menu-item ${language === code ? 'bg-red-50 text-red-700' : ''}`}
|
|
>
|
|
<span className="w-6 text-[10px] font-bold uppercase">
|
|
{code}
|
|
</span>
|
|
{name}
|
|
</DropdownMenuItem>
|
|
))}
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
);
|
|
}
|