Form Actions

This commit is contained in:
Denis Donici 2025-03-07 20:22:01 +02:00
parent 593579a1be
commit 07773ec3a7
5 changed files with 125 additions and 52 deletions

Binary file not shown.

View File

@ -4,24 +4,40 @@
import ChevronUp from './icons/ChevronUp.svelte'; import ChevronUp from './icons/ChevronUp.svelte';
import XMark from './icons/XMark.svelte'; import XMark from './icons/XMark.svelte';
import FuzzySearch from 'fuzzy-search'; import FuzzySearch from 'fuzzy-search';
// import Fuse from 'fuse.js'
type ComboBoxProps = { type ComboBoxProps = {
options: ComboOption[] | CountryBasic[] | CityBasic[]; options: readonly CountryBasic[];
onSelect: (data: ComboOption | CountryBasic | CityBasic) => void; onSelect: (data: ComboOption | CountryBasic | CityBasic) => void;
onClear: () => void; onClear: () => void;
disabledItems?: number[]; disabledItems?: number[];
selectedOptionId: number | null;
}; };
let { options, onSelect, onClear, disabledItems }: ComboBoxProps = $props(); let { options, onSelect, onClear, disabledItems, selectedOptionId }: ComboBoxProps = $props();
$effect(() => console.log(selectedOptionId));
let focused = $state(false); let focused = $state(false);
let selectSet = $state(false); let selectSet = $state(false);
let inputElement: HTMLInputElement; let inputElement: HTMLInputElement;
let dropdownElement: HTMLUListElement | null = $state(null); let dropdownElement: HTMLUListElement | null = $state(null);
let filterInput = $state('');
let emoji: string = $state(''); let emoji: string = $state('');
const ifOptionSelected = (id: number | null) => {
let textValue = '';
if (id) {
const value = options.find((option) => option.id === id)
if(value) {
emoji = 'emoji' in value ? (value.emoji ?? '') : '';
textValue = 'name' in value ? value.name : '';
selectSet = true;
}
}
return textValue;
};
let filterInput = $state(ifOptionSelected(selectedOptionId));
let searcher = $state(new FuzzySearch(options, ['name'], { caseSensitive: false, sort: true })); let searcher = $state(new FuzzySearch(options, ['name'], { caseSensitive: false, sort: true }));
$effect(() => { $effect(() => {

View File

@ -20,6 +20,19 @@ export type CityTemperature = {
max: number; max: number;
date: string; date: string;
} }
export type HotColdCity ={
city_id: number;
city_name: string;
country_id: number;
country_name: string;
max_temp: number;
min_temp: number;
date: string;
hottest_rank: number;
coldest_rank: number;
}
export type ComboOption = { export type ComboOption = {
id: number; id: number;
name: string; name: string;

View File

@ -1,9 +1,9 @@
import { allCities, allCountries, getHottestAndColdestCityInCountry } from '$lib/db/queries'; import { allCities, allCountries, getHottestAndColdestCityInCountry } from '$lib/db/queries';
import type { CityBasic, CountryBasic } from '$lib/types'; import type { CityBasic, CityTemperature, CountryBasic, HotColdCity } from '$lib/types';
import { error, type Actions } from '@sveltejs/kit'; import { type Actions } from '@sveltejs/kit';
import { getLocationTemperature } from '$lib/server/meteoService';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import { fetchAllCitiesTemperatures } from '$lib/server/meteoDataHandler'; // import { getLocationTemperature } from '$lib/server/meteoService';
// import { fetchAllCitiesTemperatures } from '$lib/server/meteoDataHandler';
const errorObject = { const errorObject = {
ok: false, ok: false,
@ -11,19 +11,36 @@ const errorObject = {
error: 'Failed to fetch temperature' error: 'Failed to fetch temperature'
} }
export const load: PageServerLoad = () => { export const load: PageServerLoad = async ({ url }) => {
try { try {
const countries = allCountries.all() as CountryBasic[]; const countries = allCountries.all() as CountryBasic[];
const cities = allCities.all() as CityBasic[]; const cities = allCities.all() as CityBasic[];
const country_id = url.searchParams.get('country');
console.log('country_id >>> ', country_id);
let temperatureData: HotColdCity[] | null = null;
if (country_id) {
temperatureData = getHottestAndColdestCityInCountry.all({
$country_id: Number(country_id),
$date: new Date().toISOString().split('T')[0]
}) as HotColdCity[];
}
console.log('Loading >>>>', temperatureData);
return { return {
countries, countries,
cities cities,
temperatureData,
country_id: country_id ? parseInt(country_id) : null
}; };
} catch (error) { } catch (error) {
console.log(error); console.log(error);
return { return {
countries: [], countries: [],
cities: [] cities: [],
temperatureData: null,
country_id: null
}; };
} }
}; };
@ -34,28 +51,25 @@ export const actions: Actions = {
const formData = await request.formData(); const formData = await request.formData();
const country_id = formData.get('country_id') as string; const country_id = formData.get('country_id') as string;
const response = await getHottestAndColdestCityInCountry.all({ console.log('FORM ACTION >>>', country_id)
const response = getHottestAndColdestCityInCountry.all({
$country_id: Number(country_id), $country_id: Number(country_id),
$date: new Date().toISOString().split('T')[0] $date: new Date().toISOString().split('T')[0]
}); }) as HotColdCity[];
console.log('RESPONSE >>>>', response);
// if (response.length == 2) { if (response.length == 2) {
// console.log(response.data); console.log('RESPONSE >>>>', response);
// return { return {
// ok: true, ok: true,
// data: response.data, data: response,
// error: null error: null,
// }; country_id: country_id ?? null
// } else { };
} else {
return errorObject; return errorObject;
// } }
// return {
// ok: true,
// data: response,
// error: null
// }
} catch (error) { } catch (error) {
console.log(error); console.log(error);
return errorObject; return errorObject;

View File

@ -1,38 +1,42 @@
<script lang="ts"> <script lang="ts">
import { enhance } from '$app/forms'; import { applyAction, enhance } from '$app/forms';
import { pushState } from '$app/navigation';
import ComboBox from '$lib/components/ComboBox.svelte'; import ComboBox from '$lib/components/ComboBox.svelte';
import Footer from '$lib/components/Footer.svelte'; import Footer from '$lib/components/Footer.svelte';
import TwoCities from '$lib/components/TwoCities.svelte'; import TwoCities from '$lib/components/TwoCities.svelte';
import type { CityBasic, CountryBasic, ComboOption } from '$lib/types'; import type { CityBasic, CountryBasic, ComboOption, HotColdCity } from '$lib/types';
import type { PageProps } from './$types'; import type { PageProps } from './$types';
let { data }: PageProps = $props(); let { data, form }: PageProps = $props();
let temperatureData: HotColdCity[] | null = $state(data.temperatureData);
let selectedCountryId: number | null = $state(data.country_id);
let countryForm: HTMLFormElement;
let submitButton: HTMLButtonElement;
let countryIdInput: HTMLInputElement;
// let filteredCities: CityBasic[] = $derived(
// data.cities.filter((c) => c.country_id == selectedCountryId)
// );
// let filteredCities: CityBasic[] = $state([]); const onCountrySelect = async (country: CountryBasic | ComboOption) => {
let selectedCountryId: number | null = $state(null);
let filteredCities: CityBasic[] = $derived(
data.cities.filter((c) => c.country_id == selectedCountryId)
);
// const setCountryCities = (id: number | null) => {
// if (id) {
// filteredCities = data.cities.filter((c) => c.country_id == id);
// } else {
// filteredCities = [];
// }
// };
const onCountrySelect = (country: CountryBasic | ComboOption) => {
selectedCountryId = country.id; selectedCountryId = country.id;
// setCountryCities(country.id); countryIdInput.value = selectedCountryId.toString();
pushState(`?country=${selectedCountryId}`, { country: selectedCountryId });
submitButton.click();
}; };
$effect(() => {
if (form && form.ok) {
temperatureData = form.data;
console.log(form.data);
}
});
</script> </script>
<svelte:head> <svelte:head>
<title>HotColdCities</title> <title>HotColdCities</title>
</svelte:head> </svelte:head>
<main class="flex h-full min-h-screen flex-col bg-white"> <main class="flex h-full min-h-screen flex-col overflow-x-hidden bg-white">
<div class="container mx-auto my-3 max-w-4xl flex-grow p-4"> <div class="container mx-auto my-3 max-w-4xl flex-grow p-4">
<h1 class="my-5 text-center text-4xl font-light">HotColdCities</h1> <h1 class="my-5 text-center text-4xl font-light">HotColdCities</h1>
<h2 class="mx-auto max-w-xl text-center text-xl leading-tight md:text-2xl"> <h2 class="mx-auto max-w-xl text-center text-xl leading-tight md:text-2xl">
@ -44,13 +48,39 @@
<div class="flex w-full flex-col gap-2"> <div class="flex w-full flex-col gap-2">
<div class="mx-auto w-full max-w-md"> <div class="mx-auto w-full max-w-md">
<ComboBox <ComboBox
options={data.countries} options={data.countries as readonly CountryBasic[]}
onSelect={onCountrySelect} onSelect={onCountrySelect}
selectedOptionId={selectedCountryId}
onClear={() => (selectedCountryId = null)} onClear={() => (selectedCountryId = null)}
/> />
</div> </div>
<form
action="?/fetchTemperature"
method="POST"
class="hidden"
use:enhance={() => {
return async ({ result }) => {
await applyAction(result);
};
}}
bind:this={countryForm}
>
<input type="number" name="country_id" bind:this={countryIdInput} readonly />
<button type="submit" bind:this={submitButton}>Submit</button>
</form>
{#if selectedCountryId && temperatureData}
<div class="my-8 flex flex-col justify-evenly gap-12 md:flex-row">
{#each temperatureData as temperature}
<dl class="flex flex-col gap-2">
<dt class="text-center text-3xl font-medium">{temperature.city_name}</dt>
<dd>Max: {temperature.max_temp}°C</dd>
<dd>Min: {temperature.min_temp}°C</dd>
</dl>
{/each}
</div>
{/if}
<!-- Add badges top 7 popular countries --> <!-- Add badges top 7 popular countries -->
<ul class="flex flex-col gap-1"> <!-- <ul class="flex flex-col gap-1">
{#each filteredCities as city (city.id)} {#each filteredCities as city (city.id)}
<li class="flex gap-2"> <li class="flex gap-2">
{city.name} {city.name}
@ -66,7 +96,7 @@
</form> </form>
</li> </li>
{/each} {/each}
</ul> </ul> -->
</div> </div>
</section> </section>
<!-- TODO: Show max min temperatures in the country and its coresponding cities --> <!-- TODO: Show max min temperatures in the country and its coresponding cities -->