mirror of
https://github.com/gevera/hot-cold-cities.git
synced 2025-12-06 10:38:20 +00:00
Add C/F units
This commit is contained in:
parent
07773ec3a7
commit
19eddc198d
3
bun.lock
3
bun.lock
@ -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=="],
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -14,7 +14,6 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
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);
|
||||||
@ -25,7 +24,7 @@
|
|||||||
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 : '';
|
||||||
@ -35,8 +34,6 @@
|
|||||||
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"
|
||||||
|
|||||||
34
src/lib/components/Navbar.svelte
Normal file
34
src/lib/components/Navbar.svelte
Normal 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>
|
||||||
6
src/lib/helpers/stores.ts
Normal file
6
src/lib/helpers/stores.ts
Normal 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')
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
export const celsiusToFahrenheit = (celsius: number): number => {
|
||||||
|
return Math.round((celsius * 9 / 5) + 32);
|
||||||
|
}
|
||||||
@ -66,3 +66,8 @@ export type MeteoData = {
|
|||||||
min: number;
|
min: number;
|
||||||
date: string;
|
date: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum UnitsEnum {
|
||||||
|
METRIC = 'METRIC',
|
||||||
|
IMPERIAL = 'IMPERIAL'
|
||||||
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user