From 90ce38820a304bc60c6bdeece56314a2b073a70c Mon Sep 17 00:00:00 2001 From: Nada Zaric Date: Wed, 22 Oct 2025 00:37:08 +0200 Subject: [PATCH 1/6] feat: Add auto-approve option in room creation and display booking status --- src/api/room.api.ts | 2 ++ src/style.css | 5 +++ src/views/room/RoomCreate.vue | 27 ++++++++++++++- src/views/room/RoomDetails.vue | 61 +++++++++++++++++++++++++++++++--- 4 files changed, 89 insertions(+), 6 deletions(-) diff --git a/src/api/room.api.ts b/src/api/room.api.ts index c1951b4..e808e95 100644 --- a/src/api/room.api.ts +++ b/src/api/room.api.ts @@ -11,6 +11,7 @@ export interface RoomDTO { maxGuests: number, photos: string[], commodities: string[], + autoApprove: boolean } export interface RoomCreateDTO { @@ -22,6 +23,7 @@ export interface RoomCreateDTO { maxGuests: number, photosPayload: string[], commodities: string[], + autoApprove: boolean } export interface CreateRoomAvailabilityListDTO { diff --git a/src/style.css b/src/style.css index 71859f2..d3b8b11 100644 --- a/src/style.css +++ b/src/style.css @@ -11,4 +11,9 @@ html a { html a:visited { color: blue; +} + +:root { + --warning-color: #f7b733; + --warning-color-text: #4b3a00; } \ No newline at end of file diff --git a/src/views/room/RoomCreate.vue b/src/views/room/RoomCreate.vue index 7350464..2fb29d8 100644 --- a/src/views/room/RoomCreate.vue +++ b/src/views/room/RoomCreate.vue @@ -6,6 +6,7 @@ import { useAuthStore } from '../../stores/auth-store'; import { useRouter } from 'vue-router'; import { RoomAPI, type RoomCreateDTO, type RoomDTO } from '../../api/room.api'; import type { FileUploadSelectEvent } from 'primevue/fileupload'; +import Checkbox from 'primevue/checkbox'; const router = useRouter(); const auth = useAuthStore(); @@ -18,7 +19,8 @@ const formDTO = ref({ minGuests: 1, maxGuests: 5, photosPayload: [], - commodities: [] + commodities: [], + autoApprove: false }); const errorCommodity = ref(''); @@ -159,6 +161,11 @@ const commoditiesIsInvalid = () => { +
+ + +
+
* { flex: 1; } + +.checkbox-field { + display: flex; + align-items: center; + gap: 0.5em; + margin-top: 1em; +} + +.checkbox-field :deep(.p-checkbox) { + /* line-height: 1; */ + margin-top: -2px; +} + +.checkbox-field label { + font-size: 1rem; + font-weight: 400; + color: var(--text-color, #333); +} \ No newline at end of file diff --git a/src/views/room/RoomDetails.vue b/src/views/room/RoomDetails.vue index a14d881..5c7a8e3 100644 --- a/src/views/room/RoomDetails.vue +++ b/src/views/room/RoomDetails.vue @@ -8,6 +8,7 @@ import RoomPriceEditor from '../../components/room/RoomPriceEditor.vue'; import { useAuthStore } from '../../stores/auth-store'; import { UserRole } from '../../api/user.api'; import { RoomImageURL } from '../../api/util'; +import Tag from 'primevue/tag'; const route = useRoute(); const router = useRouter(); @@ -74,11 +75,28 @@ const gotoReservation = () => { -

{{ room.address }}

-

{{ room.minGuests }} - {{ room.maxGuests }} guests

-
    -
  • {{ commodity }}
  • -
+
+
+ + {{ room.address }} +
+
+ + {{ room.minGuests }} - {{ room.maxGuests }} guests +
+
+ +
+ +
+
+
+ + + + +
+
@@ -129,4 +147,37 @@ const gotoReservation = () => { .inline-fields>* { flex: 1; } + +.room-info { + display: flex; + flex-direction: column; + gap: 0.75rem; + margin: 1.2rem 0; + padding: 1rem; + background-color: var(--surface-card); + border-radius: 10px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); +} + +.room-info-section { + display: flex; + align-items: center; + gap: 0.6rem; +} + +.room-icon { + color: var(--primary-color); + font-size: 1.1rem; +} + +.room-commodities { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; +} + +.warning-tag { + background-color: color-mix(in srgb, var(--warning-color) 60%, transparent); + color: var(--warning-color-text); +} \ No newline at end of file From e96011fe74dc3515f8552e789fd3b49a92833d92 Mon Sep 17 00:00:00 2001 From: Nada Zaric Date: Wed, 22 Oct 2025 19:43:41 +0200 Subject: [PATCH 2/6] feat: Display guest active reservations --- src/api/reservation.api.ts | 4 + src/views/reservation/ReservationsOfGuest.vue | 77 ++++++++++++++++--- 2 files changed, 72 insertions(+), 9 deletions(-) diff --git a/src/api/reservation.api.ts b/src/api/reservation.api.ts index bf26598..689b5b0 100644 --- a/src/api/reservation.api.ts +++ b/src/api/reservation.api.ts @@ -45,6 +45,10 @@ export class ReservationAPI { return axiosInstanceReservations.get("/req/user"); } + static getActiveReservationsByGuest(): Promise> { + return axiosInstanceReservations.get('/reservations/guest/active'); + } + static getRequestsByRoom(roomId: number): Promise> { return axiosInstanceReservations.get(`/req/room/${roomId}`); } diff --git a/src/views/reservation/ReservationsOfGuest.vue b/src/views/reservation/ReservationsOfGuest.vue index 6a0aca8..dcfe7c4 100644 --- a/src/views/reservation/ReservationsOfGuest.vue +++ b/src/views/reservation/ReservationsOfGuest.vue @@ -3,16 +3,19 @@ import { onMounted, ref } from 'vue'; import { useRoute, useRouter } from 'vue-router'; import { useAuthStore } from '../../stores/auth-store'; -import { ReservationAPI, ReservationRequestStatus, type CreateReservationRequestDTO, type ReservationRequestDTO } from '../../api/reservation.api'; +import { ReservationAPI, ReservationRequestStatus, type ReservationDTO, type CreateReservationRequestDTO, type ReservationRequestDTO } from '../../api/reservation.api'; import type { AxiosError, AxiosResponse } from 'axios'; import { useToast } from 'primevue'; const route = useRoute(); const router = useRouter(); const auth = useAuthStore(); + +const reservations = ref([]); const reservationRequests = ref([]); const userId = ref(-1); +const reservationsError = ref(''); const reservationRequestError = ref(''); const loading = ref(false); @@ -30,8 +33,17 @@ onMounted(() => { const loadReservations = () => { loading.value = true; + reservationsError.value = ''; reservationRequestError.value = ''; + ReservationAPI.getActiveReservationsByGuest().then((res: AxiosResponse) => { + reservations.value = res.data; + }).catch((err: AxiosError) => { + reservationsError.value = err.message; + }).finally(() => { + loading.value = false; + }); + ReservationAPI.getRequestsByGuest().then((res: AxiosResponse) => { reservationRequests.value = res.data; }).catch((err: AxiosError) => { @@ -41,6 +53,10 @@ const loadReservations = () => { }); } +const deleteResrvation = (reservationRequestId: number) => { + // TODO: Implement +} + const deleteRequest = (reservationRequestId: number) => { ReservationAPI.deleteRequest(reservationRequestId).then((res) => { loadReservations(); @@ -55,25 +71,67 @@ const deleteRequest = (reservationRequestId: number) => {

Your reservations

-
+
You have no reservations.
-
- -
+ + + + + + + + + + + + + + + + + + + + + + + + + + {{ reservationsError }} +

Your requests

-
+
You have no active reservation requests.
- + - + @@ -81,7 +139,8 @@ const deleteRequest = (reservationRequestId: number) => { {{ new Date(slotProps.data.dateFrom).toDateString() }} - + + From bd608045c4ef85336a66e796f1291e5bf6c3c541 Mon Sep 17 00:00:00 2001 From: Nada Zaric Date: Wed, 22 Oct 2025 21:33:50 +0200 Subject: [PATCH 3/6] feat: Add page for displaying active reservations of host --- src/api/reservation.api.ts | 4 + src/components/Navbar.vue | 2 +- src/router/index.ts | 2 + src/views/reservation/ReservationsOfHost.vue | 94 ++++++++++++++++++++ 4 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 src/views/reservation/ReservationsOfHost.vue diff --git a/src/api/reservation.api.ts b/src/api/reservation.api.ts index 689b5b0..768a8f7 100644 --- a/src/api/reservation.api.ts +++ b/src/api/reservation.api.ts @@ -49,6 +49,10 @@ export class ReservationAPI { return axiosInstanceReservations.get('/reservations/guest/active'); } + static getActiveReservationsByHost(): Promise> { + return axiosInstanceReservations.get('/reservations/host/active'); + } + static getRequestsByRoom(roomId: number): Promise> { return axiosInstanceReservations.get(`/req/room/${roomId}`); } diff --git a/src/components/Navbar.vue b/src/components/Navbar.vue index 942eefb..14efef4 100644 --- a/src/components/Navbar.vue +++ b/src/components/Navbar.vue @@ -65,7 +65,7 @@ const computeNavbar = () => { case UserRole.Host: navbarItems.value.push({ label: 'New Room', icon: 'pi pi-plus-circle', command: () => goto('/new-room') }); navbarItems.value.push({ label: 'My Rooms', icon: 'pi pi-building', command: () => goto('/my-rooms') }); - navbarItems.value.push({ label: 'Reservations', icon: 'pi pi-address-book', command: () => goto('/') }); + navbarItems.value.push({ label: 'Reservations', icon: 'pi pi-address-book', command: () => goto(`/reservation/host/${auth.id}`) }); navbarItems.value.push({ label: 'Notifications', icon: 'pi pi-bell', command: () => goto('/') }); navbarItems.value.push({ label: 'Ratings', icon: 'pi pi-star', command: () => goto('/') }); break; diff --git a/src/router/index.ts b/src/router/index.ts index 4de64ea..7bb454e 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -12,6 +12,7 @@ import RoomsOfHost from '../views/room/RoomsOfHost.vue'; import ReservationCreate from '../views/reservation/ReservationCreate.vue'; import ReservationsOfGuest from '../views/reservation/ReservationsOfGuest.vue'; import RoomFind from '../views/room/RoomFind.vue'; +import ReservationsOfHost from '../views/reservation/ReservationsOfHost.vue'; /** * A special type for route guards. @@ -41,6 +42,7 @@ const routes: RouteRecordRaw[] = [ { path: '/new-room', component: RoomCreate, meta: { role: [UserRole.Host] } }, { path: '/room/:id', component: RoomDetails, meta: { role: 'all' } }, { path: '/my-rooms', component: RoomsOfHost, meta: { role: [UserRole.Host] } }, + { path: '/reservation/host/:id', component: ReservationsOfHost, meta: { role: [UserRole.Host] } }, { path: `/reservation/new/:id`, component: ReservationCreate, meta: { role: [UserRole.Guest] } }, { path: `/reservation/user/:id`, component: ReservationsOfGuest, meta: { role: [UserRole.Guest] } }, diff --git a/src/views/reservation/ReservationsOfHost.vue b/src/views/reservation/ReservationsOfHost.vue new file mode 100644 index 0000000..747c703 --- /dev/null +++ b/src/views/reservation/ReservationsOfHost.vue @@ -0,0 +1,94 @@ + + + + + \ No newline at end of file From c11cb2a1a75d499879abe2dae2113b99e901cc12 Mon Sep 17 00:00:00 2001 From: Nada Zaric Date: Wed, 22 Oct 2025 22:12:29 +0200 Subject: [PATCH 4/6] feat: Display reservation requests on room page --- src/components/Navbar.vue | 2 +- .../room/RoomActiveRequestEditor.vue | 91 +++++++++++++++++++ src/router/index.ts | 2 +- src/views/reservation/ReservationsOfHost.vue | 5 +- src/views/room/RoomDetails.vue | 6 ++ 5 files changed, 101 insertions(+), 5 deletions(-) create mode 100644 src/components/room/RoomActiveRequestEditor.vue diff --git a/src/components/Navbar.vue b/src/components/Navbar.vue index 14efef4..f13670d 100644 --- a/src/components/Navbar.vue +++ b/src/components/Navbar.vue @@ -65,7 +65,7 @@ const computeNavbar = () => { case UserRole.Host: navbarItems.value.push({ label: 'New Room', icon: 'pi pi-plus-circle', command: () => goto('/new-room') }); navbarItems.value.push({ label: 'My Rooms', icon: 'pi pi-building', command: () => goto('/my-rooms') }); - navbarItems.value.push({ label: 'Reservations', icon: 'pi pi-address-book', command: () => goto(`/reservation/host/${auth.id}`) }); + navbarItems.value.push({ label: 'Reservations', icon: 'pi pi-address-book', command: () => goto(`/reservation/host`) }); navbarItems.value.push({ label: 'Notifications', icon: 'pi pi-bell', command: () => goto('/') }); navbarItems.value.push({ label: 'Ratings', icon: 'pi pi-star', command: () => goto('/') }); break; diff --git a/src/components/room/RoomActiveRequestEditor.vue b/src/components/room/RoomActiveRequestEditor.vue new file mode 100644 index 0000000..d7ae568 --- /dev/null +++ b/src/components/room/RoomActiveRequestEditor.vue @@ -0,0 +1,91 @@ + + + + + \ No newline at end of file diff --git a/src/router/index.ts b/src/router/index.ts index 7bb454e..7f42b3e 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -42,7 +42,7 @@ const routes: RouteRecordRaw[] = [ { path: '/new-room', component: RoomCreate, meta: { role: [UserRole.Host] } }, { path: '/room/:id', component: RoomDetails, meta: { role: 'all' } }, { path: '/my-rooms', component: RoomsOfHost, meta: { role: [UserRole.Host] } }, - { path: '/reservation/host/:id', component: ReservationsOfHost, meta: { role: [UserRole.Host] } }, + { path: '/reservation/host', component: ReservationsOfHost, meta: { role: [UserRole.Host] } }, { path: `/reservation/new/:id`, component: ReservationCreate, meta: { role: [UserRole.Guest] } }, { path: `/reservation/user/:id`, component: ReservationsOfGuest, meta: { role: [UserRole.Guest] } }, diff --git a/src/views/reservation/ReservationsOfHost.vue b/src/views/reservation/ReservationsOfHost.vue index 747c703..1389402 100644 --- a/src/views/reservation/ReservationsOfHost.vue +++ b/src/views/reservation/ReservationsOfHost.vue @@ -17,10 +17,9 @@ const userId = ref(-1); onMounted(() => { auth.checkLocalStorage(); + userId.value = auth.id - userId.value = parseInt(route.params.id as string); - - if (auth.id != userId.value) { + if (auth.id !== userId.value) { router.push('/'); } diff --git a/src/views/room/RoomDetails.vue b/src/views/room/RoomDetails.vue index 5c7a8e3..a8b8f96 100644 --- a/src/views/room/RoomDetails.vue +++ b/src/views/room/RoomDetails.vue @@ -9,6 +9,7 @@ import { useAuthStore } from '../../stores/auth-store'; import { UserRole } from '../../api/user.api'; import { RoomImageURL } from '../../api/util'; import Tag from 'primevue/tag'; +import RoomActiveRequestEditor from '../../components/room/RoomActiveRequestEditor.vue'; const route = useRoute(); const router = useRouter(); @@ -67,6 +68,7 @@ const gotoReservation = () => { @@ -118,6 +120,10 @@ const gotoReservation = () => { + + + +
From 27c25ec986e93c2c9541b6b8134d7914908d7cac Mon Sep 17 00:00:00 2001 From: Nada Zaric Date: Wed, 22 Oct 2025 22:41:06 +0200 Subject: [PATCH 5/6] feat: Implement reject reservation request action for hosts --- src/api/reservation.api.ts | 4 ++++ .../room/RoomActiveRequestEditor.vue | 19 +++++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/api/reservation.api.ts b/src/api/reservation.api.ts index 768a8f7..66e9fd3 100644 --- a/src/api/reservation.api.ts +++ b/src/api/reservation.api.ts @@ -66,4 +66,8 @@ export class ReservationAPI { params: { from, to }, }); } + + static rejectRequest(id: number): Promise> { + return axiosInstanceReservations.put(`/req/${id}/reject`); + } } diff --git a/src/components/room/RoomActiveRequestEditor.vue b/src/components/room/RoomActiveRequestEditor.vue index d7ae568..add8e21 100644 --- a/src/components/room/RoomActiveRequestEditor.vue +++ b/src/components/room/RoomActiveRequestEditor.vue @@ -31,8 +31,20 @@ const acceptRequest = (reservationRequestId: number) => { } const rejectRequest = (reservationRequestId: number) => { - // TODO: Implement -} + loading.value = true; + requestsError.value = ''; + + ReservationAPI.rejectRequest(reservationRequestId) + .then(() => { + loadRequests(); + }) + .catch((err: AxiosError) => { + requestsError.value = err.message; + }) + .finally(() => { + loading.value = false; + }); +}; - \ No newline at end of file + \ No newline at end of file From d7b5a0f28ce644b016baeeabddbc0e0ee72d0d45 Mon Sep 17 00:00:00 2001 From: Nada Zaric Date: Thu, 23 Oct 2025 00:16:26 +0200 Subject: [PATCH 6/6] feat: Implement approve reservation request action for hosts --- src/api/reservation.api.ts | 6 +++++- src/components/room/RoomActiveRequestEditor.vue | 17 +++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/api/reservation.api.ts b/src/api/reservation.api.ts index 66e9fd3..833a136 100644 --- a/src/api/reservation.api.ts +++ b/src/api/reservation.api.ts @@ -70,4 +70,8 @@ export class ReservationAPI { static rejectRequest(id: number): Promise> { return axiosInstanceReservations.put(`/req/${id}/reject`); } -} + + static approveRequest(id: number): Promise> { + return axiosInstanceReservations.put(`/req/${id}/approve`); + } +} \ No newline at end of file diff --git a/src/components/room/RoomActiveRequestEditor.vue b/src/components/room/RoomActiveRequestEditor.vue index add8e21..ce1e86b 100644 --- a/src/components/room/RoomActiveRequestEditor.vue +++ b/src/components/room/RoomActiveRequestEditor.vue @@ -27,8 +27,21 @@ const loadRequests = () => { }; const acceptRequest = (reservationRequestId: number) => { - // TODO: Implement -} + loading.value = true; + requestsError.value = ''; + + ReservationAPI.approveRequest(reservationRequestId) + .then(() => { + loadRequests(); + }) + .catch((err: AxiosError) => { + requestsError.value = err.message; + }) + .finally(() => { + loading.value = false; + }); +}; + const rejectRequest = (reservationRequestId: number) => { loading.value = true;