diff --git a/src/api/reservation.api.ts b/src/api/reservation.api.ts index bf26598..833a136 100644 --- a/src/api/reservation.api.ts +++ b/src/api/reservation.api.ts @@ -45,6 +45,14 @@ export class ReservationAPI { return axiosInstanceReservations.get("/req/user"); } + static getActiveReservationsByGuest(): Promise> { + 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}`); } @@ -58,4 +66,12 @@ export class ReservationAPI { params: { from, to }, }); } -} + + 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/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/components/Navbar.vue b/src/components/Navbar.vue index 942eefb..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('/') }); + 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..ce1e86b --- /dev/null +++ b/src/components/room/RoomActiveRequestEditor.vue @@ -0,0 +1,115 @@ + + + + + \ No newline at end of file diff --git a/src/router/index.ts b/src/router/index.ts index 4de64ea..7f42b3e 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', 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/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/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() }} - + + diff --git a/src/views/reservation/ReservationsOfHost.vue b/src/views/reservation/ReservationsOfHost.vue new file mode 100644 index 0000000..1389402 --- /dev/null +++ b/src/views/reservation/ReservationsOfHost.vue @@ -0,0 +1,93 @@ + + + + + \ 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..a8b8f96 100644 --- a/src/views/room/RoomDetails.vue +++ b/src/views/room/RoomDetails.vue @@ -8,6 +8,8 @@ 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'; +import RoomActiveRequestEditor from '../../components/room/RoomActiveRequestEditor.vue'; const route = useRoute(); const router = useRouter(); @@ -66,6 +68,7 @@ const gotoReservation = () => { @@ -74,11 +77,28 @@ const gotoReservation = () => { -

{{ room.address }}

-

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

-
    -
  • {{ commodity }}
  • -
+
+
+ + {{ room.address }} +
+
+ + {{ room.minGuests }} - {{ room.maxGuests }} guests +
+
+ +
+ +
+
+
+ + + + +
+
@@ -100,6 +120,10 @@ const gotoReservation = () => { + + + +
@@ -129,4 +153,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