Skip to content

Commit

Permalink
feat: add / edit visited countries
Browse files Browse the repository at this point in the history
  • Loading branch information
johanohly committed Oct 3, 2024
1 parent e70c5dc commit 38abf16
Show file tree
Hide file tree
Showing 7 changed files with 297 additions and 36 deletions.
1 change: 1 addition & 0 deletions src/lib/components/modals/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export { default as ListFlightsModal } from '$lib/components/modals/list-flights
export { default as StatisticsModal } from './statistics/StatisticsModal.svelte';
export { default as EditFlightModal } from './edit-flight/EditFlightModal.svelte';
export { default as SetupVisitedCountries } from './visited-countries/SetupVisitedCountries.svelte';
export { default as EditVisitedCountry } from './visited-countries/EditVisitedCountry.svelte';
106 changes: 106 additions & 0 deletions src/lib/components/modals/visited-countries/EditVisitedCountry.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<script lang="ts">
import { Modal } from '$lib/components/ui/modal';
import type { VisitedCountryStatus } from '$lib/db/types';
import { Label } from '$lib/components/ui/label';
import { House, Gift, Plane } from '@o7/icon/lucide';
import { Tour } from '@o7/icon/material';
import { type Country, countryFromNumeric } from '$lib/utils/data/countries';
import { Button } from '$lib/components/ui/button';
import { api, trpc } from '$lib/trpc';
import { toast } from 'svelte-sonner';
import { Textarea } from '$lib/components/ui/input/index.js';
import { cn } from '$lib/utils';
let {
open = $bindable(),
editingInfo,
}: {
open: boolean;
editingInfo: {
id: number;
status: (typeof VisitedCountryStatus)[number] | null;
note: string | null;
} | null;
} = $props();
let countryData: Country | undefined = $state(undefined);
let status: (typeof VisitedCountryStatus)[number] | null = $state(null);
let note: string = $state('');
$effect(() => {
if (!editingInfo) {
status = null;
note = '';
return;
}
countryData = countryFromNumeric(+editingInfo.id);
status = editingInfo?.status;
note = editingInfo.note ?? '';
});
const save = async () => {
if (!countryData) return;
const success = await api.visitedCountries.save.mutate({
code: countryData.numeric,
status,
note: note ?? undefined,
});
if (success) {
await trpc.visitedCountries.list.utils.invalidate();
open = false;
} else {
toast.error('Failed to save');
}
};
</script>

<Modal bind:open>
<h2 class="text-lg font-bold max-md:mb-2">{countryData?.name}</h2>
<div class="grid grid-cols-2 gap-2">
{@render statusRadioItem('visited', 'Visited')}
{@render statusRadioItem('lived', 'Lived')}
{@render statusRadioItem('wishlist', 'Wishlist')}
{@render statusRadioItem('layover', 'Layover')}
</div>
<Textarea bind:value={note} placeholder="Note" class="w-full mt-2" />
<Button
onclick={save}
disabled={(editingInfo?.status === status && editingInfo?.note === note) ||
(!status && !editingInfo?.status)}
>
>Save
</Button>
</Modal>

{#snippet statusRadioItem(value, label)}
<Label
onclick={() => {
if (status === value) {
status = null;
} else {
status = value;
}
}}
class={cn('w-full cursor-pointer', {
'[&>div]:border-primary': value === status,
})}
>
<div
class="border-muted bg-popover hover:bg-accent items-center rounded-md border-2 p-4"
>
<div class="flex items-center justify-center gap-1">
{#if value === 'lived'}
<House class="w-6 h-6" />
{:else if value === 'visited'}
<Tour class="w-6 h-6" />
{:else if value === 'wishlist'}
<Gift class="w-6 h-6" />
{:else if value === 'layover'}
<Plane class="w-6 h-6" />
{/if}
<span class="text-2xl font-bold">{label}</span>
</div>
</div>
</Label>
{/snippet}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
let { visitedCountries }: { visitedCountries: any[] } = $props();
let open = $derived.by(() => visitedCountries.length === 0);
let manualOverride = $state(false);
let open = $derived.by(
() => !manualOverride && visitedCountries.length === 0,
);
let loading = $state(false);
const importFlights = async () => {
Expand All @@ -29,5 +32,10 @@
<Button onclick={importFlights} disabled={loading}
>Fill from your flights
</Button>
<Button variant="outline" disabled={loading}>Fill manually</Button>
<Button
onclick={() => (manualOverride = true)}
variant="outline"
disabled={loading}
>Start from scratch
</Button>
</Modal>
7 changes: 7 additions & 0 deletions src/lib/db/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,10 @@ export const SeatClasses = [
'private',
] as const;
export const FlightReasons = ['leisure', 'business', 'crew', 'other'] as const;

export const VisitedCountryStatus = [
'lived',
'visited',
'layover',
'wishlist',
] as const;
37 changes: 37 additions & 0 deletions src/lib/server/routes/visited-countries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ import { db } from '$lib/db';
import { listFlights } from '$lib/server/utils/flight';
import { airportFromICAO } from '$lib/utils/data/airports';
import { countryFromAlpha } from '$lib/utils/data/countries';
import { z } from 'zod';
import { VisitedCountryStatus } from '$lib/db/types';

const VisitedCountrySchema = z.object({
code: z.number(),
status: z.enum(VisitedCountryStatus).nullable(),
note: z.string().nullable(),
});

export const visitedCountriesRouter = router({
list: authedProcedure.query(async ({ ctx }) => {
Expand All @@ -12,6 +20,35 @@ export const visitedCountriesRouter = router({
.where('userId', '=', ctx.user.id)
.execute();
}),
save: authedProcedure
.input(VisitedCountrySchema)
.mutation(async ({ ctx, input }) => {
const status = input.status;
if (status) {
const result = await db
.insertInto('visitedCountry')
.values({
userId: ctx.user.id,
code: input.code,
status,
note: input.note,
})
.onConflict((oc) =>
oc
.columns(['userId', 'code'])
.doUpdateSet({ status, note: input.note }),
)
.execute();
return result.length > 0;
} else {
const result = await db
.deleteFrom('visitedCountry')
.where('userId', '=', ctx.user.id)
.where('code', '=', input.code)
.execute();
return result.length > 0;
}
}),
importFlights: authedProcedure.mutation(async ({ ctx }) => {
const flights = await listFlights(ctx.user.id);
const countries: number[] = [];
Expand Down
6 changes: 5 additions & 1 deletion src/lib/utils/data/countries.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { COUNTRIES } from '$lib/data/countries';

type Country = (typeof COUNTRIES)[number];
export type Country = (typeof COUNTRIES)[number];

export const countryFromAlpha = (alpha: string): Country | undefined => {
return COUNTRIES.find((country) => country.alpha === alpha);
};

export const countryFromNumeric = (numeric: number): Country | undefined => {
return COUNTRIES.find((country) => country.numeric === numeric);
};
Loading

0 comments on commit 38abf16

Please sign in to comment.