mirror of
https://github.com/gevera/hot-cold-cities.git
synced 2025-12-06 10:38:20 +00:00
Form Actions
This commit is contained in:
parent
593579a1be
commit
07773ec3a7
BIN
database/data.db
BIN
database/data.db
Binary file not shown.
@ -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(() => {
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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[];
|
||||||
|
|
||||||
|
if (response.length == 2) {
|
||||||
console.log('RESPONSE >>>>', response);
|
console.log('RESPONSE >>>>', response);
|
||||||
|
|
||||||
// if (response.length == 2) {
|
return {
|
||||||
// console.log(response.data);
|
ok: true,
|
||||||
|
data: response,
|
||||||
// return {
|
error: null,
|
||||||
// ok: true,
|
country_id: country_id ?? null
|
||||||
// data: response.data,
|
};
|
||||||
// error: 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;
|
||||||
|
|||||||
@ -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 -->
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user