From 9f2b38e871b1133c003fbf1e6be5a9fccbf2302d Mon Sep 17 00:00:00 2001 From: Yannik Inniger Date: Sat, 28 Oct 2023 17:55:41 +0200 Subject: [PATCH 01/12] =?UTF-8?q?Implement=20date=20selction=20with=20card?= =?UTF-8?q?=20=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.css | 96 +++++++++---------- src/model/reservation.model.ts | 19 +++- src/model/reservation.store.ts | 18 +++- src/routes/calendar/Calendar.svelte | 33 ++++++- src/routes/calendar/components/Booking.svelte | 2 + .../components/ReservationCard.svelte | 13 +++ src/utils/date.utils.ts | 23 +++++ 7 files changed, 149 insertions(+), 55 deletions(-) create mode 100644 src/routes/calendar/components/Booking.svelte create mode 100644 src/routes/calendar/components/ReservationCard.svelte create mode 100644 src/utils/date.utils.ts diff --git a/src/app.css b/src/app.css index b87aec7..f5cec05 100644 --- a/src/app.css +++ b/src/app.css @@ -1,80 +1,80 @@ :root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - -webkit-text-size-adjust: 100%; + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; } a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; + font-weight: 500; + color: #646cff; + text-decoration: inherit; } a:hover { - color: #535bf2; + color: #535bf2; } body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; + margin: 0; + display: flex; + justify-content: flex-start; + min-width: 320px; + min-height: 100vh; } h1 { - font-size: 3.2em; - line-height: 1.1; + font-size: 3.2em; + line-height: 1.1; } .card { - padding: 2em; + padding: 2em; } #app { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; } button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; } button:hover { - border-color: #646cff; + border-color: #646cff; } button:focus, button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; + outline: 4px auto -webkit-focus-ring-color; } @media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } } diff --git a/src/model/reservation.model.ts b/src/model/reservation.model.ts index 2ec3e3a..eaba28f 100644 --- a/src/model/reservation.model.ts +++ b/src/model/reservation.model.ts @@ -1,8 +1,25 @@ -export interface Reservation { +import { timestampToDate } from '../utils/date.utils'; + +export interface ReservationDto { id: string; owner: string; startTime: number; //timestamp endTime: number; //timestamp } +export interface Reservation { + id: string; + owner: string; + startTime: Date; + endTime: Date; +} + export type ReservationStoreData = Reservation[] | null; + +export const mapDtoToReservation = (dto: ReservationDto): Reservation => { + return { + ...dto, + startTime: timestampToDate(dto.startTime), + endTime: timestampToDate(dto.endTime), + }; +}; diff --git a/src/model/reservation.store.ts b/src/model/reservation.store.ts index 1577ebf..2ea77e7 100644 --- a/src/model/reservation.store.ts +++ b/src/model/reservation.store.ts @@ -1,7 +1,12 @@ import { writable } from 'svelte/store'; -import type { Reservation, ReservationStoreData } from './reservation.model'; +import { isSameDay } from '../utils/date.utils'; +import { + mapDtoToReservation, + type ReservationDto, + type ReservationStoreData, +} from './reservation.model'; -const mockedReservations: Reservation[] = [ +const mockedReservations: ReservationDto[] = [ { id: '1', owner: '1', @@ -16,8 +21,11 @@ const mockedReservations: Reservation[] = [ }, ]; -const reservations$ = writable(null); +export const reservationStore = writable(null); -export const loadSlots = (): Reservation[] => { - return mockedReservations; +export const loadReservationsForDay = (date: Date) => { + const reservations = mockedReservations + .map(mapDtoToReservation) + .filter((reservation) => isSameDay(date, reservation.startTime)); + reservationStore.set(reservations); }; diff --git a/src/routes/calendar/Calendar.svelte b/src/routes/calendar/Calendar.svelte index 7e43061..5c30e7d 100644 --- a/src/routes/calendar/Calendar.svelte +++ b/src/routes/calendar/Calendar.svelte @@ -1,8 +1,39 @@ -

{pickedDate}

+ +{#if selecedDate} +

{convertToDateString(selecedDate)}

+
+ {#if hasReservations} + {#each reservations as reservation} + + {/each} + {:else} +

There are no reservations for today

+ {/if} +
+{/if} diff --git a/src/routes/calendar/components/Booking.svelte b/src/routes/calendar/components/Booking.svelte new file mode 100644 index 0000000..0fbba99 --- /dev/null +++ b/src/routes/calendar/components/Booking.svelte @@ -0,0 +1,2 @@ + diff --git a/src/routes/calendar/components/ReservationCard.svelte b/src/routes/calendar/components/ReservationCard.svelte new file mode 100644 index 0000000..04e7325 --- /dev/null +++ b/src/routes/calendar/components/ReservationCard.svelte @@ -0,0 +1,13 @@ + + +
+

+ {convertToTime(reservation.startTime)} - + {convertToTime(reservation.endTime)} +

+
diff --git a/src/utils/date.utils.ts b/src/utils/date.utils.ts new file mode 100644 index 0000000..579d7d7 --- /dev/null +++ b/src/utils/date.utils.ts @@ -0,0 +1,23 @@ +export const timestampToDate = (timestamp: number): Date => { + return new Date(timestamp * 1000); +}; + +export const isSameDay = (dateA: Date, dateB: Date) => { + var dateAStr = dateA.toISOString().slice(0, 10); + var dateBStr = dateB.toISOString().slice(0, 10); + + return dateAStr === dateBStr; +}; + +export const convertToDateString = (date: Date) => + date.toLocaleDateString('en-us', { + day: '2-digit', + month: 'short', + weekday: 'long', + }); + +export const convertToTime = (date: Date) => + date.toLocaleTimeString('de-ch', { + hour: '2-digit', + minute: '2-digit', + }); From 3ceb631914db43cc4bbe78c70b0ee1eec91b6889 Mon Sep 17 00:00:00 2001 From: Yannik Inniger Date: Sat, 28 Oct 2023 19:09:57 +0200 Subject: [PATCH 02/12] =?UTF-8?q?Model=20changes=20becuase=20of=20Beulter?= =?UTF-8?q?=F0=9F=A6=ABs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/base.model.ts | 16 ++++++++++++++++ src/model/machine.model.ts | 5 +++++ src/model/reservation.model.ts | 17 ++++++++--------- src/model/reservation.store.ts | 8 ++++---- 4 files changed, 33 insertions(+), 13 deletions(-) create mode 100644 src/model/base.model.ts create mode 100644 src/model/machine.model.ts diff --git a/src/model/base.model.ts b/src/model/base.model.ts new file mode 100644 index 0000000..979575a --- /dev/null +++ b/src/model/base.model.ts @@ -0,0 +1,16 @@ +export interface StartEndTimeDto { + start_time: string; //timestamp + end_time: string; //timestamp +} + +export interface StartEndTime { + startTime: Date; + endTime: Date; +} + +export const startEndTimeDtoMapper = (dto: StartEndTimeDto): StartEndTime => { + return { + startTime: new Date(dto.start_time), + endTime: new Date(dto.end_time), + }; +}; diff --git a/src/model/machine.model.ts b/src/model/machine.model.ts new file mode 100644 index 0000000..a7718f5 --- /dev/null +++ b/src/model/machine.model.ts @@ -0,0 +1,5 @@ +export interface Machine { + id: string; + name: string; + property: string; +} diff --git a/src/model/reservation.model.ts b/src/model/reservation.model.ts index eaba28f..9522d25 100644 --- a/src/model/reservation.model.ts +++ b/src/model/reservation.model.ts @@ -1,17 +1,17 @@ -import { timestampToDate } from '../utils/date.utils'; +import { + startEndTimeDtoMapper, + type StartEndTime, + type StartEndTimeDto, +} from './base.model'; -export interface ReservationDto { +export interface ReservationDto extends StartEndTimeDto { id: string; owner: string; - startTime: number; //timestamp - endTime: number; //timestamp } -export interface Reservation { +export interface Reservation extends StartEndTime { id: string; owner: string; - startTime: Date; - endTime: Date; } export type ReservationStoreData = Reservation[] | null; @@ -19,7 +19,6 @@ export type ReservationStoreData = Reservation[] | null; export const mapDtoToReservation = (dto: ReservationDto): Reservation => { return { ...dto, - startTime: timestampToDate(dto.startTime), - endTime: timestampToDate(dto.endTime), + ...startEndTimeDtoMapper(dto), }; }; diff --git a/src/model/reservation.store.ts b/src/model/reservation.store.ts index 2ea77e7..bf70594 100644 --- a/src/model/reservation.store.ts +++ b/src/model/reservation.store.ts @@ -10,14 +10,14 @@ const mockedReservations: ReservationDto[] = [ { id: '1', owner: '1', - startTime: 1698667200, - endTime: 1698670800, + start_time: '2023-10-30T12:00:00.00+01:00', + end_time: '2023-10-30T13:00:00.00+01:00', }, { id: '2', owner: '2', - startTime: 1698678000, - endTime: 1698681600, + start_time: '2023-10-30T15:00:00.00+01:00', + end_time: '2023-10-30T16:00:00.00+01:00', }, ]; From 60e8e7e2667bbd026dd0e4d0ab2954a407c3d812 Mon Sep 17 00:00:00 2001 From: Michael Beutler Date: Sat, 28 Oct 2023 21:30:26 +0200 Subject: [PATCH 03/12] =?UTF-8?q?Added=20action=20and=20docker=20config.?= =?UTF-8?q?=20=F0=9F=90=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/publish.yml | 50 +++++++++++++++++++++++++++++++++++ Dockerfile | 17 ++++++++++++ conf/default.conf | 19 +++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 .github/workflows/publish.yml create mode 100644 Dockerfile create mode 100644 conf/default.conf diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..1507423 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,50 @@ +name: 🐳 Publish Docker image + +on: + push: + branches: + - main + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + push_to_registry: + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Check out the repo + uses: actions/checkout@v1 + with: + submodules: recursive + + - name: Log in to GitHub packages + uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Declare some variables + shell: bash + run: | + echo "sha_short=$(git rev-parse --short "$GITHUB_SHA")" >> "$GITHUB_ENV" + echo "branch=$(echo ${GITHUB_REF#refs/heads/})" >> "$GITHUB_ENV" + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + - name: Build and push Docker image + uses: docker/build-push-action@v2 + with: + context: . + push: true + tags: ${{ env.sha_short }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..729c8a7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM node:18 AS build + +WORKDIR /app + +COPY package.json ./ +COPY package-lock.json ./ +RUN npm install + +COPY . ./ +RUN npm run build + +FROM nginx:1.25-alpine + +# Copy the config files +ADD ./conf/ /etc/nginx/conf.d/ + +COPY --from=build /app/dist /usr/share/nginx/html diff --git a/conf/default.conf b/conf/default.conf new file mode 100644 index 0000000..c71dcf2 --- /dev/null +++ b/conf/default.conf @@ -0,0 +1,19 @@ +server { + listen 80; + server_name localhost; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + try_files $uri $uri/ /index.html; + } + + #error_page 404 /404.html; + + # redirect server error pages to the static page /50x.html + # + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} \ No newline at end of file From 48dbe5e81a5282fe1a1e4825ade20caf48677dd2 Mon Sep 17 00:00:00 2001 From: Michael Beutler Date: Sat, 28 Oct 2023 21:41:02 +0200 Subject: [PATCH 04/12] =?UTF-8?q?Added=20new=20permissions.=20=F0=9F=94=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/publish.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1507423..1535dcd 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -14,8 +14,16 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest permissions: - contents: read + contents: write packages: write + deployments: write + statuses: write + actions: write + checks: write + issues: write + pull-requests: write + security-events: write + steps: - name: Check out the repo uses: actions/checkout@v1 From 994af2363e2a81e26b06fa85ea253a720490f6cf Mon Sep 17 00:00:00 2001 From: Michael Beutler Date: Sat, 28 Oct 2023 21:46:15 +0200 Subject: [PATCH 05/12] =?UTF-8?q?Added=20new=20permissions.=20=F0=9F=94=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/publish.yml | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1535dcd..4362774 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -14,16 +14,8 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest permissions: - contents: write + contents: read packages: write - deployments: write - statuses: write - actions: write - checks: write - issues: write - pull-requests: write - security-events: write - steps: - name: Check out the repo uses: actions/checkout@v1 @@ -54,5 +46,5 @@ jobs: with: context: . push: true - tags: ${{ env.sha_short }} + tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} From 30d1fc4ac44ec30414ceba07659c0d84d631a610 Mon Sep 17 00:00:00 2001 From: Yannik Inniger Date: Sat, 28 Oct 2023 22:20:13 +0200 Subject: [PATCH 06/12] =?UTF-8?q?Implement=20booking=20=F0=9F=8F=84?= =?UTF-8?q?=E2=80=8D=E2=99=82=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.svelte | 7 +++ src/consts.ts | 4 ++ src/model/reservation.store.ts | 45 ++++++++++++---- src/routes/calendar/Calendar.svelte | 51 ++++++++++++++++-- src/routes/calendar/components/Booking.svelte | 26 ++++++++++ .../components/BookingTimeSelection.svelte | 52 +++++++++++++++++++ .../components/ReservationCard.svelte | 5 +- src/utils/date.extenstions.ts | 47 +++++++++++++++++ src/utils/date.utils.ts | 27 ++++------ 9 files changed, 227 insertions(+), 37 deletions(-) create mode 100644 src/consts.ts create mode 100644 src/routes/calendar/components/BookingTimeSelection.svelte create mode 100644 src/utils/date.extenstions.ts diff --git a/src/App.svelte b/src/App.svelte index ced269c..7b14610 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -3,6 +3,7 @@ import { Link, Route, Router } from 'svelte-routing'; import Calendar from './routes/calendar/Calendar.svelte'; import LostAndFound from './routes/lost-and-found/LostAndFound.svelte'; + import './utils/date.extenstions'; @@ -15,3 +16,9 @@ + + diff --git a/src/consts.ts b/src/consts.ts new file mode 100644 index 0000000..fddd9df --- /dev/null +++ b/src/consts.ts @@ -0,0 +1,4 @@ +export const DEFAULT_BOOKING_HOURS = 1; +export const DEFAULT_DAY_START = 8; +export const DEFAULT_DAY_END = 22; +export const ONE_HOUR_IN_MS = 3600000; diff --git a/src/model/reservation.store.ts b/src/model/reservation.store.ts index bf70594..5e534b6 100644 --- a/src/model/reservation.store.ts +++ b/src/model/reservation.store.ts @@ -1,31 +1,54 @@ import { writable } from 'svelte/store'; -import { isSameDay } from '../utils/date.utils'; import { mapDtoToReservation, + type Reservation, type ReservationDto, type ReservationStoreData, } from './reservation.model'; const mockedReservations: ReservationDto[] = [ - { - id: '1', - owner: '1', - start_time: '2023-10-30T12:00:00.00+01:00', - end_time: '2023-10-30T13:00:00.00+01:00', - }, { id: '2', owner: '2', start_time: '2023-10-30T15:00:00.00+01:00', end_time: '2023-10-30T16:00:00.00+01:00', }, + { + id: '1', + owner: '1', + start_time: '2023-10-30T12:00:00.00+01:00', + end_time: '2023-10-30T13:00:00.00+01:00', + }, ]; export const reservationStore = writable(null); -export const loadReservationsForDay = (date: Date) => { - const reservations = mockedReservations - .map(mapDtoToReservation) - .filter((reservation) => isSameDay(date, reservation.startTime)); +const sortReservations = (reservations: Reservation[]) => { + return reservations?.sort((reservationA, reservationB) => + reservationA.startTime.isAfter(reservationB.startTime) ? -1 : 1 + ); +}; + +export const loadReservationsForDay = (date: Date): void => { + const reservations = sortReservations( + mockedReservations + .map(mapDtoToReservation) + .filter((reservation) => date.isSameDay(reservation.startTime)) + ); reservationStore.set(reservations); }; + +export const createReservation = (startTime: Date, endTime: Date): void => { + reservationStore.update((reservations) => { + const newReservations = !!reservations ? reservations : []; + return sortReservations([ + ...newReservations, + { + id: 'new', + owner: '1', + startTime, + endTime, + }, + ]); + }); +}; diff --git a/src/routes/calendar/Calendar.svelte b/src/routes/calendar/Calendar.svelte index 5c30e7d..99a1baa 100644 --- a/src/routes/calendar/Calendar.svelte +++ b/src/routes/calendar/Calendar.svelte @@ -1,11 +1,16 @@ - + {#if selecedDate} -

{convertToDateString(selecedDate)}

{#if hasReservations} - {#each reservations as reservation} +

Reservations for: {selecedDate.toWeekdayString()}

+ {#each reservations as reservation, i (i)} + {#if showBookingButton(reservation.endTime, getAvailableUntil(i))} + + {/if} {/each} {:else} -

There are no reservations for today

+

There are no reservations for: {selecedDate.toWeekdayString()}

+ {/if}
{/if} diff --git a/src/routes/calendar/components/Booking.svelte b/src/routes/calendar/components/Booking.svelte index 0fbba99..d751e72 100644 --- a/src/routes/calendar/components/Booking.svelte +++ b/src/routes/calendar/components/Booking.svelte @@ -1,2 +1,28 @@ + +
+ {#if !addingBooking} + + {:else} + + {/if} +
diff --git a/src/routes/calendar/components/BookingTimeSelection.svelte b/src/routes/calendar/components/BookingTimeSelection.svelte new file mode 100644 index 0000000..52fcb5f --- /dev/null +++ b/src/routes/calendar/components/BookingTimeSelection.svelte @@ -0,0 +1,52 @@ + + +
+ + {#if endTime} +

End time: {endTime.toTime()}

+ {/if} +
+ + +
+
+ + diff --git a/src/routes/calendar/components/ReservationCard.svelte b/src/routes/calendar/components/ReservationCard.svelte index 04e7325..9f96589 100644 --- a/src/routes/calendar/components/ReservationCard.svelte +++ b/src/routes/calendar/components/ReservationCard.svelte @@ -1,13 +1,12 @@

- {convertToTime(reservation.startTime)} - - {convertToTime(reservation.endTime)} + {reservation.startTime.toTime()} - + {reservation.endTime.toTime()}

diff --git a/src/utils/date.extenstions.ts b/src/utils/date.extenstions.ts new file mode 100644 index 0000000..4cb18a8 --- /dev/null +++ b/src/utils/date.extenstions.ts @@ -0,0 +1,47 @@ +export {}; + +declare global { + interface Date { + addHours(days: number): Date; + isSameDay(otherDate: Date): boolean; + toWeekdayString(): string; + toTime(): string; + isAfter(otherDate: Date): boolean; + isBefore(otherDate: Date): boolean; + } +} + +Date.prototype.addHours = function (hours: number) { + this.setTime(this.getTime() + hours * 60 * 60 * 1000); + return this; +}; + +Date.prototype.isSameDay = function (otherDate: Date): boolean { + var thisDateStr = this.toISOString().slice(0, 10); + var otherDateStr = otherDate.toISOString().slice(0, 10); + + return thisDateStr === otherDateStr; +}; + +Date.prototype.toWeekdayString = function (): string { + return this.toLocaleDateString('en-us', { + day: '2-digit', + month: 'short', + weekday: 'long', + }); +}; + +Date.prototype.toTime = function (): string { + return this.toLocaleTimeString('de-ch', { + hour: '2-digit', + minute: '2-digit', + }); +}; + +Date.prototype.isAfter = function (otherDate: Date): boolean { + return this.getTime() < otherDate.getTime(); +}; + +Date.prototype.isBefore = function (otherDate: Date): boolean { + return this.getTime() > otherDate.getTime(); +}; diff --git a/src/utils/date.utils.ts b/src/utils/date.utils.ts index 579d7d7..4318e21 100644 --- a/src/utils/date.utils.ts +++ b/src/utils/date.utils.ts @@ -2,22 +2,13 @@ export const timestampToDate = (timestamp: number): Date => { return new Date(timestamp * 1000); }; -export const isSameDay = (dateA: Date, dateB: Date) => { - var dateAStr = dateA.toISOString().slice(0, 10); - var dateBStr = dateB.toISOString().slice(0, 10); - - return dateAStr === dateBStr; +export const newDateWithTime = (date: Date, time: string): Date => { + const newDate = new Date(date); + const hoursMinuteTuple = time.split(':'); + newDate.setHours( + parseInt(hoursMinuteTuple[0]), + parseInt(hoursMinuteTuple[1]) + ); + + return newDate; }; - -export const convertToDateString = (date: Date) => - date.toLocaleDateString('en-us', { - day: '2-digit', - month: 'short', - weekday: 'long', - }); - -export const convertToTime = (date: Date) => - date.toLocaleTimeString('de-ch', { - hour: '2-digit', - minute: '2-digit', - }); From cd2b2faf4e61e51b552e33e47bc3efa5f874995c Mon Sep 17 00:00:00 2001 From: Yannik Inniger Date: Sat, 28 Oct 2023 22:24:11 +0200 Subject: [PATCH 07/12] =?UTF-8?q?Spanish=20teatime=20=F0=9F=AB=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routes/home/Home.svelte | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/routes/home/Home.svelte diff --git a/src/routes/home/Home.svelte b/src/routes/home/Home.svelte new file mode 100644 index 0000000..d40c9c2 --- /dev/null +++ b/src/routes/home/Home.svelte @@ -0,0 +1 @@ +

¡Hola soy una tetera 🫖!

From 065dfcaa701edad5954c72f200e30c934f22dd2a Mon Sep 17 00:00:00 2001 From: Yannik Inniger Date: Sat, 28 Oct 2023 22:26:30 +0200 Subject: [PATCH 08/12] =?UTF-8?q?The=20path=20of=20tea=20=E2=98=95?= =?UTF-8?q?=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.svelte | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/App.svelte b/src/App.svelte index 7b14610..d7fd9b8 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -2,18 +2,21 @@ import '@picocss/pico/css/pico.min.css'; import { Link, Route, Router } from 'svelte-routing'; import Calendar from './routes/calendar/Calendar.svelte'; + import Home from './routes/home/Home.svelte'; import LostAndFound from './routes/lost-and-found/LostAndFound.svelte'; import './utils/date.extenstions';
- + +
From 8dc355ec4a008976dedc3eef401bf9325a10eb62 Mon Sep 17 00:00:00 2001 From: Yannik Inniger Date: Sun, 29 Oct 2023 01:16:47 +0200 Subject: [PATCH 09/12] =?UTF-8?q?The=20tea=20is=20strong=20in=20you=20?= =?UTF-8?q?=F0=9F=90=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/consts.ts | 3 + src/lib/Teapot.svelte | 60 +++++++++++++++++++ src/model/machine.model.ts | 2 +- src/model/reservation.model.ts | 4 +- src/model/reservation.store.ts | 47 ++++++++++----- src/model/user.model.ts | 6 ++ src/model/user.store.ts | 13 ++++ src/routes/calendar/Calendar.svelte | 20 +++---- .../calendar/components/DateSelector.svelte | 29 +++++++++ src/routes/home/BookingSelection.svelte | 17 ++++++ src/routes/home/Home.svelte | 36 ++++++++++- src/utils/date.extenstions.ts | 5 ++ src/utils/rest.utils.ts | 15 +++++ 13 files changed, 227 insertions(+), 30 deletions(-) create mode 100644 src/lib/Teapot.svelte create mode 100644 src/model/user.model.ts create mode 100644 src/model/user.store.ts create mode 100644 src/routes/calendar/components/DateSelector.svelte create mode 100644 src/routes/home/BookingSelection.svelte create mode 100644 src/utils/rest.utils.ts diff --git a/src/consts.ts b/src/consts.ts index fddd9df..02f9529 100644 --- a/src/consts.ts +++ b/src/consts.ts @@ -1,4 +1,7 @@ +export const BASE_URL = 'https://api.iperka.com'; + export const DEFAULT_BOOKING_HOURS = 1; export const DEFAULT_DAY_START = 8; export const DEFAULT_DAY_END = 22; + export const ONE_HOUR_IN_MS = 3600000; diff --git a/src/lib/Teapot.svelte b/src/lib/Teapot.svelte new file mode 100644 index 0000000..489b7ca --- /dev/null +++ b/src/lib/Teapot.svelte @@ -0,0 +1,60 @@ + + +{#if showTeapot} +
+ {#await getTeapot()} +

Brewing tea 🫖...

+ {:then body} +

I'm a teapot 🫖

+
{body}
+ + {:catch error} +

Tea brewed too long and burned 🫖🔥

+ + {/await} +
+{/if} + + diff --git a/src/model/machine.model.ts b/src/model/machine.model.ts index a7718f5..51b91d8 100644 --- a/src/model/machine.model.ts +++ b/src/model/machine.model.ts @@ -1,5 +1,5 @@ export interface Machine { - id: string; + id: string; // uuid name: string; property: string; } diff --git a/src/model/reservation.model.ts b/src/model/reservation.model.ts index 9522d25..13bc351 100644 --- a/src/model/reservation.model.ts +++ b/src/model/reservation.model.ts @@ -5,12 +5,12 @@ import { } from './base.model'; export interface ReservationDto extends StartEndTimeDto { - id: string; + id?: string; owner: string; } export interface Reservation extends StartEndTime { - id: string; + id?: string; owner: string; } diff --git a/src/model/reservation.store.ts b/src/model/reservation.store.ts index 5e534b6..3dd1550 100644 --- a/src/model/reservation.store.ts +++ b/src/model/reservation.store.ts @@ -1,4 +1,5 @@ import { writable } from 'svelte/store'; +import { post } from '../utils/rest.utils'; import { mapDtoToReservation, type Reservation, @@ -23,32 +24,50 @@ const mockedReservations: ReservationDto[] = [ export const reservationStore = writable(null); -const sortReservations = (reservations: Reservation[]) => { +const sortReservations = (reservations: Reservation[]): Reservation[] => { return reservations?.sort((reservationA, reservationB) => reservationA.startTime.isAfter(reservationB.startTime) ? -1 : 1 ); }; +const filterReservations = + (dateToFilterFor: Date) => + (reservation: Reservation): boolean => { + const now = new Date(); + let isValid = dateToFilterFor.isSameDay(reservation.startTime); + if (now.isSameDay(dateToFilterFor)) { + isValid = isValid && reservation.startTime.isAfter(now); + } + return isValid; + }; + +// public methods + export const loadReservationsForDay = (date: Date): void => { const reservations = sortReservations( mockedReservations .map(mapDtoToReservation) - .filter((reservation) => date.isSameDay(reservation.startTime)) + .filter(filterReservations(date)) ); reservationStore.set(reservations); }; -export const createReservation = (startTime: Date, endTime: Date): void => { - reservationStore.update((reservations) => { - const newReservations = !!reservations ? reservations : []; - return sortReservations([ - ...newReservations, - { - id: 'new', - owner: '1', - startTime, - endTime, - }, - ]); +export const createReservation = async ( + user: string, + startTime: Date, + endTime: Date +): Promise => { + post('/reservations', { + owner: user, + start_time: startTime.toISOString(), + end_time: endTime.toISOString(), + }).then((json) => { + reservationStore.update((reservations) => { + const newReservations = !!reservations ? reservations : []; + return sortReservations([ + ...newReservations, + mapDtoToReservation(json), + ]); + }); }); }; diff --git a/src/model/user.model.ts b/src/model/user.model.ts new file mode 100644 index 0000000..520bfee --- /dev/null +++ b/src/model/user.model.ts @@ -0,0 +1,6 @@ +export interface User { + id: string; // uuid + name: string; +} + +export type UserStoreData = User | null; diff --git a/src/model/user.store.ts b/src/model/user.store.ts new file mode 100644 index 0000000..e6f088e --- /dev/null +++ b/src/model/user.store.ts @@ -0,0 +1,13 @@ +import { writable } from 'svelte/store'; +import type { User, UserStoreData } from './user.model'; + +const mockUser: User = { + id: '1', + name: '1', +}; + +export const userStore = writable(null); + +export const loadUserByApartmentNumber = (apartmentNumber: number) => { + userStore.set(mockUser); +}; diff --git a/src/routes/calendar/Calendar.svelte b/src/routes/calendar/Calendar.svelte index 99a1baa..b960318 100644 --- a/src/routes/calendar/Calendar.svelte +++ b/src/routes/calendar/Calendar.svelte @@ -4,20 +4,19 @@ DEFAULT_DAY_START, ONE_HOUR_IN_MS, } from '../../consts'; - import Datepicker from '../../lib/Datepicker.svelte'; import type { Reservation } from '../../model/reservation.model'; import { loadReservationsForDay, reservationStore, } from '../../model/reservation.store'; import Booking from './components/Booking.svelte'; + import DateSelector from './components/DateSelector.svelte'; import ReservationCard from './components/ReservationCard.svelte'; - let pickedDate: Date; + let selecedDate: Date | undefined; let hasReservations: boolean = false; let reservations: Reservation[]; - $: selecedDate = !!pickedDate ? new Date(pickedDate) : undefined; $: if (selecedDate) loadReservationsForDay(selecedDate); reservationStore.subscribe((allReservations) => { @@ -28,7 +27,7 @@ }); const getAvailableUntil = (index: number): Date => { - let defaultEnd = getPickedDateWithTime(DEFAULT_DAY_END); + let defaultEnd = getSelectedDateWithTime(DEFAULT_DAY_END); if (index < reservations.length) { const nextReservation = reservations.at(index + 1); @@ -42,17 +41,14 @@ return until.getTime() - from.getTime() >= ONE_HOUR_IN_MS; }; - const getPickedDateWithTime = (time: number): Date => { - let date = new Date(pickedDate); + const getSelectedDateWithTime = (time: number): Date => { + let date = new Date(selecedDate!); date.setHours(time); return date; }; - + {#if selecedDate}
@@ -72,8 +68,8 @@

There are no reservations for: {selecedDate.toWeekdayString()}

{/if}
diff --git a/src/routes/calendar/components/DateSelector.svelte b/src/routes/calendar/components/DateSelector.svelte new file mode 100644 index 0000000..d83ab0f --- /dev/null +++ b/src/routes/calendar/components/DateSelector.svelte @@ -0,0 +1,29 @@ + + +
+ + + +
+ + diff --git a/src/routes/home/BookingSelection.svelte b/src/routes/home/BookingSelection.svelte new file mode 100644 index 0000000..a651f06 --- /dev/null +++ b/src/routes/home/BookingSelection.svelte @@ -0,0 +1,17 @@ + + +

When would you like to wash?

+ + diff --git a/src/routes/home/Home.svelte b/src/routes/home/Home.svelte index d40c9c2..ff6c884 100644 --- a/src/routes/home/Home.svelte +++ b/src/routes/home/Home.svelte @@ -1 +1,35 @@ -

¡Hola soy una tetera 🫖!

+ + +{#if !$userStore} +

¡Hola soy una tetera 🫖!

+ + +{:else} + +{/if} + + diff --git a/src/utils/date.extenstions.ts b/src/utils/date.extenstions.ts index 4cb18a8..53260ab 100644 --- a/src/utils/date.extenstions.ts +++ b/src/utils/date.extenstions.ts @@ -6,6 +6,7 @@ declare global { isSameDay(otherDate: Date): boolean; toWeekdayString(): string; toTime(): string; + toShortISOString(): string; isAfter(otherDate: Date): boolean; isBefore(otherDate: Date): boolean; } @@ -45,3 +46,7 @@ Date.prototype.isAfter = function (otherDate: Date): boolean { Date.prototype.isBefore = function (otherDate: Date): boolean { return this.getTime() > otherDate.getTime(); }; + +Date.prototype.toShortISOString = function (): string { + return this.toISOString().split('T')[0]; +}; diff --git a/src/utils/rest.utils.ts b/src/utils/rest.utils.ts new file mode 100644 index 0000000..f99fb27 --- /dev/null +++ b/src/utils/rest.utils.ts @@ -0,0 +1,15 @@ +import { BASE_URL } from '../consts'; + +export const get = (endpoint: string) => { + fetch(`${BASE_URL}${endpoint}`, { + method: 'GET', + }).then((response: Response) => response.json()); +}; + +export const post = (endpoint: string, body: T): Promise => { + const json = JSON.stringify(body); + return fetch(`${BASE_URL}${endpoint}`, { + method: 'POST', + body: json, + }).then((response: Response) => response.json()); +}; From 19e9e9fe5f83c8bf708debf37dc322777648a75a Mon Sep 17 00:00:00 2001 From: Yannik Inniger Date: Sun, 29 Oct 2023 01:28:34 +0200 Subject: [PATCH 10/12] =?UTF-8?q?Load=20user=20from=20backend,=20thanks=20?= =?UTF-8?q?to=20the=20=F0=9F=A6=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/model/user.store.ts | 15 +++++++++------ src/utils/rest.utils.ts | 10 +++++----- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/model/user.store.ts b/src/model/user.store.ts index e6f088e..7298080 100644 --- a/src/model/user.store.ts +++ b/src/model/user.store.ts @@ -1,13 +1,16 @@ import { writable } from 'svelte/store'; +import { get } from '../utils/rest.utils'; import type { User, UserStoreData } from './user.model'; -const mockUser: User = { - id: '1', - name: '1', -}; - export const userStore = writable(null); export const loadUserByApartmentNumber = (apartmentNumber: number) => { - userStore.set(mockUser); + get<{ data: User[] }>('/users') + .then((response) => response.data) + .then((users) => { + const user = users.find( + (user) => user.name === apartmentNumber.toString() + ); + userStore.set(user ?? null); + }); }; diff --git a/src/utils/rest.utils.ts b/src/utils/rest.utils.ts index f99fb27..a077a0d 100644 --- a/src/utils/rest.utils.ts +++ b/src/utils/rest.utils.ts @@ -1,15 +1,15 @@ import { BASE_URL } from '../consts'; -export const get = (endpoint: string) => { - fetch(`${BASE_URL}${endpoint}`, { +export const get = async (endpoint: string) => { + return fetch(`${BASE_URL}${endpoint}`, { method: 'GET', - }).then((response: Response) => response.json()); + }).then((response: Response) => response.json() as T); }; -export const post = (endpoint: string, body: T): Promise => { +export const post = async (endpoint: string, body: T): Promise => { const json = JSON.stringify(body); return fetch(`${BASE_URL}${endpoint}`, { method: 'POST', body: json, - }).then((response: Response) => response.json()); + }).then((response: Response) => response.json() as T); }; From 3d7401717047e6bc3291217bad17b4058a7e67d1 Mon Sep 17 00:00:00 2001 From: Yannik Inniger Date: Sun, 29 Oct 2023 01:38:47 +0200 Subject: [PATCH 11/12] =?UTF-8?q?Some=20date=20magic=20=F0=9F=AA=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/Datepicker.svelte | 2 +- .../calendar/components/DateSelector.svelte | 22 ++++++++++++++++--- src/utils/date.extenstions.ts | 8 ++++++- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/lib/Datepicker.svelte b/src/lib/Datepicker.svelte index d19dca1..ddbd76e 100644 --- a/src/lib/Datepicker.svelte +++ b/src/lib/Datepicker.svelte @@ -1,6 +1,6 @@
diff --git a/src/routes/calendar/components/DateSelector.svelte b/src/routes/calendar/components/DateSelector.svelte index d83ab0f..81b086c 100644 --- a/src/routes/calendar/components/DateSelector.svelte +++ b/src/routes/calendar/components/DateSelector.svelte @@ -2,18 +2,34 @@ import Datepicker from '../../../lib/Datepicker.svelte'; export let selecedDate: Date | undefined; - let pickedDate: Date; + let pickedDate: string; $: selecedDate = !!pickedDate ? new Date(pickedDate) : undefined; + + const nextDay = () => { + if (!selecedDate) { + selecedDate = new Date(); + } + selecedDate.addDays(1); + pickedDate = selecedDate.toShortISOString(); + }; + + const previousDay = () => { + if (!selecedDate) { + selecedDate = new Date(); + } + selecedDate.addDays(-1); + pickedDate = selecedDate.toShortISOString(); + };
- + - +