Add C/F units

This commit is contained in:
Denis Donici 2025-03-07 21:41:57 +02:00
parent 07773ec3a7
commit 19eddc198d
9 changed files with 92 additions and 21 deletions

View File

@ -29,6 +29,7 @@
"prettier-plugin-tailwindcss": "^0.6.11", "prettier-plugin-tailwindcss": "^0.6.11",
"svelte": "^5.22.5", "svelte": "^5.22.5",
"svelte-check": "^4.1.5", "svelte-check": "^4.1.5",
"svelte-persisted-store": "^0.12.0",
"tailwindcss": "^4.0.11", "tailwindcss": "^4.0.11",
"typescript": "^5.8.2", "typescript": "^5.8.2",
"typescript-eslint": "^8.26.0", "typescript-eslint": "^8.26.0",
@ -561,6 +562,8 @@
"svelte-eslint-parser": ["svelte-eslint-parser@1.0.1", "", { "dependencies": { "eslint-scope": "^8.2.0", "eslint-visitor-keys": "^4.0.0", "espree": "^10.0.0", "postcss": "^8.4.49", "postcss-scss": "^4.0.9", "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" }, "optionalPeers": ["svelte"] }, "sha512-JjdEMXOJqy+dxeaElxbN+meTOtVpHfLnq9VGpiTAOLgM0uHO+ogmUsA3IFgx0x3Wl15pqTZWycCikcD7cAQN/g=="], "svelte-eslint-parser": ["svelte-eslint-parser@1.0.1", "", { "dependencies": { "eslint-scope": "^8.2.0", "eslint-visitor-keys": "^4.0.0", "espree": "^10.0.0", "postcss": "^8.4.49", "postcss-scss": "^4.0.9", "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" }, "optionalPeers": ["svelte"] }, "sha512-JjdEMXOJqy+dxeaElxbN+meTOtVpHfLnq9VGpiTAOLgM0uHO+ogmUsA3IFgx0x3Wl15pqTZWycCikcD7cAQN/g=="],
"svelte-persisted-store": ["svelte-persisted-store@0.12.0", "", { "peerDependencies": { "svelte": "^3.48.0 || ^4 || ^5" } }, "sha512-BdBQr2SGSJ+rDWH8/aEV5GthBJDapVP0GP3fuUCA7TjYG5ctcB+O9Mj9ZC0+Jo1oJMfZUd1y9H68NFRR5MyIJA=="],
"tailwindcss": ["tailwindcss@4.0.11", "", {}, "sha512-GZ6+tNwieqvpFLZfx2tkZpfOMAK7iumbOJOLmd6v8AcYuHbjUb+cmDRu6l+rFkIqarh5FfLbCSRJhegcVdoPng=="], "tailwindcss": ["tailwindcss@4.0.11", "", {}, "sha512-GZ6+tNwieqvpFLZfx2tkZpfOMAK7iumbOJOLmd6v8AcYuHbjUb+cmDRu6l+rFkIqarh5FfLbCSRJhegcVdoPng=="],
"tapable": ["tapable@2.2.1", "", {}, "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ=="], "tapable": ["tapable@2.2.1", "", {}, "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ=="],

View File

@ -24,6 +24,7 @@
"prettier-plugin-tailwindcss": "^0.6.11", "prettier-plugin-tailwindcss": "^0.6.11",
"svelte": "^5.22.5", "svelte": "^5.22.5",
"svelte-check": "^4.1.5", "svelte-check": "^4.1.5",
"svelte-persisted-store": "^0.12.0",
"tailwindcss": "^4.0.11", "tailwindcss": "^4.0.11",
"typescript": "^5.8.2", "typescript": "^5.8.2",
"typescript-eslint": "^8.26.0", "typescript-eslint": "^8.26.0",

View File

@ -14,19 +14,18 @@
}; };
let { options, onSelect, onClear, disabledItems, selectedOptionId }: 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 emoji: string = $state(''); let emoji: string = $state('');
const ifOptionSelected = (id: number | null) => { const ifOptionSelected = (id: number | null) => {
let textValue = ''; let textValue = '';
if (id) { if (id) {
const value = options.find((option) => option.id === id) const value = options.find((option) => option.id === id);
if(value) { if (value) {
emoji = 'emoji' in value ? (value.emoji ?? '') : ''; emoji = 'emoji' in value ? (value.emoji ?? '') : '';
textValue = 'name' in value ? value.name : ''; textValue = 'name' in value ? value.name : '';
selectSet = true; selectSet = true;
@ -34,9 +33,7 @@
} }
return textValue; return textValue;
}; };
let filterInput = $state(ifOptionSelected(selectedOptionId)); 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 }));
@ -85,8 +82,8 @@
}); });
</script> </script>
<div class="dropdown my-2 py-2 w-full"> <div class="dropdown my-2 w-full py-2">
<label class="input !outline-none focus:ring-0 input-lg w-full"> <label class="input input-lg w-full !outline-none focus:ring-0">
<input <input
type="text" type="text"
class="w-full focus:ring-0 focus:outline-none" class="w-full focus:ring-0 focus:outline-none"

View File

@ -0,0 +1,34 @@
<script lang="ts">
import { units, unitsSymbol } from '$lib/helpers/stores';
import { UnitsEnum } from '$lib/types';
</script>
<div class="navbar bg-gray-50 shadow-sm">
<div class="flex-1">
<a href="/" class="btn btn-ghost text-xl">daisyUI</a>
</div>
<div class="flex-none">
<div class="dropdown dropdown-bottom dropdown-end">
<div tabindex="0" role="button" class="btn btn-ghost btn-square m-1">{$unitsSymbol}</div>
<ul tabindex="0" class="dropdown-content menu bg-base-100 rounded-box z-1 w-32 p-2 shadow-sm">
<li onclick={() => ($units = UnitsEnum.METRIC)}>
<a>°C metric</a>
</li>
<li onclick={() => ($units = UnitsEnum.IMPERIAL)}>
<a>°F imperial</a>
</li>
</ul>
</div>
<!-- <li> -->
<!-- <details> -->
<!-- <summary>Parent</summary> -->
<!-- <ul class="bg-base-100 w-36 rounded-t-none p-2"> -->
<!-- <li><a class="inline-block w-full"><span>°C metric</span></a></li> -->
<!-- <li><a class="inline-block w-full"><span>°F imperial</span></a></li> -->
<!-- </ul> -->
<!-- </details> -->
<!-- </li> -->
<!-- </ul> -->
</div>
</div>

View File

@ -0,0 +1,6 @@
import { UnitsEnum } from '$lib/types';
import { persisted } from 'svelte-persisted-store';
import { derived } from 'svelte/store';
export const units = persisted('units', UnitsEnum.METRIC)
export const unitsSymbol = derived(units, ($units) => $units == UnitsEnum.IMPERIAL ? '°F' : '°C')

View File

@ -0,0 +1,3 @@
export const celsiusToFahrenheit = (celsius: number): number => {
return Math.round((celsius * 9 / 5) + 32);
}

View File

@ -21,7 +21,7 @@ export type CityTemperature = {
date: string; date: string;
} }
export type HotColdCity ={ export type HotColdCity = {
city_id: number; city_id: number;
city_name: string; city_name: string;
country_id: number; country_id: number;
@ -66,3 +66,8 @@ export type MeteoData = {
min: number; min: number;
date: string; date: string;
} }
export enum UnitsEnum {
METRIC = 'METRIC',
IMPERIAL = 'IMPERIAL'
}

View File

@ -16,7 +16,7 @@ export const load: PageServerLoad = async ({ url }) => {
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'); const country_id = url.searchParams.get('country');
console.log('country_id >>> ', country_id); // console.log('country_id >>> ', country_id);
let temperatureData: HotColdCity[] | null = null; let temperatureData: HotColdCity[] | null = null;
if (country_id) { if (country_id) {
@ -26,7 +26,7 @@ export const load: PageServerLoad = async ({ url }) => {
}) as HotColdCity[]; }) as HotColdCity[];
} }
console.log('Loading >>>>', temperatureData); // console.log('Loading >>>>', temperatureData);
return { return {
countries, countries,
@ -51,14 +51,14 @@ 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;
console.log('FORM ACTION >>>', country_id) // console.log('FORM ACTION >>>', country_id)
const response = getHottestAndColdestCityInCountry.all({ 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[]; }) as HotColdCity[];
if (response.length == 2) { if (response.length == 2) {
console.log('RESPONSE >>>>', response); // console.log('RESPONSE >>>>', response);
return { return {
ok: true, ok: true,

View File

@ -1,10 +1,19 @@
<script lang="ts"> <script lang="ts">
import { applyAction, enhance } from '$app/forms'; import { applyAction, enhance } from '$app/forms';
import { pushState } from '$app/navigation'; import { pushState, replaceState } 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 Navbar from '$lib/components/Navbar.svelte';
import TwoCities from '$lib/components/TwoCities.svelte'; import TwoCities from '$lib/components/TwoCities.svelte';
import type { CityBasic, CountryBasic, ComboOption, HotColdCity } from '$lib/types'; import { units, unitsSymbol } from '$lib/helpers/stores';
import { celsiusToFahrenheit } from '$lib/helpers/utils';
import {
type CityBasic,
type CountryBasic,
type ComboOption,
type HotColdCity,
UnitsEnum
} from '$lib/types';
import type { PageProps } from './$types'; import type { PageProps } from './$types';
let { data, form }: PageProps = $props(); let { data, form }: PageProps = $props();
@ -24,10 +33,14 @@
submitButton.click(); submitButton.click();
}; };
const handleClearCountry = () => {
replaceState('/', {});
selectedCountryId = null;
};
$effect(() => { $effect(() => {
if (form && form.ok) { if (form && form.ok) {
temperatureData = form.data; temperatureData = form.data;
console.log(form.data);
} }
}); });
</script> </script>
@ -37,6 +50,7 @@
</svelte:head> </svelte:head>
<main class="flex h-full min-h-screen flex-col overflow-x-hidden bg-white"> <main class="flex h-full min-h-screen flex-col overflow-x-hidden bg-white">
<Navbar />
<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">
@ -51,7 +65,7 @@
options={data.countries as readonly CountryBasic[]} options={data.countries as readonly CountryBasic[]}
onSelect={onCountrySelect} onSelect={onCountrySelect}
selectedOptionId={selectedCountryId} selectedOptionId={selectedCountryId}
onClear={() => (selectedCountryId = null)} onClear={handleClearCountry}
/> />
</div> </div>
<form <form
@ -73,8 +87,16 @@
{#each temperatureData as temperature} {#each temperatureData as temperature}
<dl class="flex flex-col gap-2"> <dl class="flex flex-col gap-2">
<dt class="text-center text-3xl font-medium">{temperature.city_name}</dt> <dt class="text-center text-3xl font-medium">{temperature.city_name}</dt>
<dd>Max: {temperature.max_temp}°C</dd> <dd>
<dd>Min: {temperature.min_temp}°C</dd> Max: {$units == UnitsEnum.IMPERIAL
? celsiusToFahrenheit(temperature.max_temp)
: temperature.min_temp}{$unitsSymbol}
</dd>
<dd>
Min: {$units == UnitsEnum.IMPERIAL
? celsiusToFahrenheit(temperature.min_temp)
: temperature.min_temp}{$unitsSymbol}
</dd>
</dl> </dl>
{/each} {/each}
</div> </div>