= Имплементација на кориснички сценарија == Општи информации Нашата апликација, како што е наведено и во претходните фази, се темели на клиент-сервер архитектура. Во овој случај клиентската страна е „претставена“ преку React апликација, додека пак серверската страна е Spring Boot апликација. Во основа на оваа архитектура е комуникација меѓу клиентот и серверот преку HTTP барања. За визуелизација и давање можност за интеракција на крајниот корисник, кој управува директно со апликацијата на клиентска страна, беше потребно да воспоставуваме конекција и соодветно да ги обработуваме простите податоци кои пристигнуваат од серверската страна. Ова го постигнавме преку Axios HTTP клиентот кој е инсталиран на клиенстката апликација и неговата инстанца во продолжение е онаа преку која ги испраќаме сите барања до серверот. {{{#!javascript import axios from "axios"; const instance = axios.create({ baseURL: "http://localhost:8080/", withCredentials: true, maxRedirects: 1, }) export default instance }}} На овој начин обезбедуваме при секое барање основниот URL да биде основниот URL на серверската апликација, додека пак со поставување на параметарот withCredentials овозможуваме препраќање на колачињата и XMLHttpRequest, што овозможува прибавување на нови податоци без целосно одновно вчитување на страницата. === Custom React Hook-и За поедноставно справување со HTTP GET и POST барањата, кои како што претходно споменавме се главната врска меѓу клиентската и серверската страна и практично не е можно да постои компонента каде не се употребуваат, креиравме наши React Hook-и кои се реискористливи и можат да се употребуваат во различни сценарија со едноставна промена на URL-то ==== useGet {{{#!javascript import axios from "../../axios"; import {useState, useEffect, useContext} from 'react'; const useGet = (url) => { const [data, setData] = useState(null); const [isLoading, setIsLoading] = useState(true); const [changed, setChanged] = useState(0) const getData = async (uurl) => { await axios.get(uurl}).then((res) => { setData(res.data); }).catch((error) => { console.log(error) window.location.href = '/error' }) .finally(() => { setIsLoading(false); }); }; useEffect(() => { setIsLoading(true); getData(url); }, [dep, url, changed]); return { data, setData, isLoading, getData, setChanged }; }; export default useGet; }}} Како што и самото име кажува, овој Hook го користиме за GET барања кон серверската страна. При инстанцирање се наведува URL кон кое се праќаат барањата. За чување на податоците кои се добиваат како одговор се користи концептот на useState од React што овозможува ререндерирање на сите компоненти при промена на состојбата на објектот. На сличен начин се постапува и со знаменцето за вчитување на податоци, кое при испраќање на барањето се поставува на true, додека пак кога ќе заврши обработката се враќа на false. Истото служи како guard clause за компонентите кои ги користат податоците кои се чекаат од GET барањето, како би се избегнале исклучоци во случај кога рендерирањето на компонентата ќе заврши пред податоците да бидат вчитани. Асинхроната функција која ги прибавува податоците од серверот е ставена во useEffect callback, што овозможува нејзино реизвршување при промена на некој од елементите во низата зависности. Овој концепт го искористивме за да овозможиме на едноставен начин ререднерирање на компонентите во кои додаваме нови записи, преку вметнување на дополнителна вредност/состојба changed во низата зависноти која пак, при праќање на барање кое очекуваме да предизвика промена на податоците достапни на страницата, се менува и со тоа автоматски се испраќа ново GET барање и промените се одразуваат на компонените кои се зависни од нив. GET барањето испратено преку Axios HTTP клиентот враќа Promise со кој соодветно се справуваме на горенаведениот начин: - во then сегментот состојбата на data се поставува на податоците кои се вратени од серверот (ова значи дека барањето поминало со статус 200); - catch сегментот се извршува кога одговорот на барањето е со статус 4ХХ, односно грешка, па во овој случај корисникот се пренасочува до страница со грешка - finally сегментот се извршува во секој случај, односно означува дека комуникацијата е завршена, вратен е одговор од серверот, па тука се поставува знаменцето isLoading назад на false ==== usePost {{{#!javascript import axios from "../../axios"; const useCreate = () => { const createEntity = async (url, entity, getData) => { console.log(entity) await axios .post(url, null, { params: entity, }) .then((res) => { getData(prev => ++prev) }) .catch((err) => { console.log(err); }) .finally(() => { }); } return { createEntity }; } export default useCreate; }}} Овој Hook го користиме за POST барања кон серверската страна. Од него постојат неколку модифицирани верзии во зависоност од тоа дали парамтерите се испраќаат во телото на барањето, како URL параметри или како променливи во патеката, но во основа се работи за иста имплементација. За разлика од useGet, тука, URL-то се испраќа како аргумент на функцијата, заедно со податоците кои треба да се испратат како параметри и состојбата на зависната променлива за која зборувавме во претходната компонент. Откако ќе се испрати POST, se враќа Promise со кој соодветно се справуваме на горенаведениот начин, со единствна разлика што во then сегментот, кога статусот означува успех, ја менуваме состојбата на зависната променлива за да се ререндерираат зависните компоненти. ==== useFormData {{{#!javascript import { useState } from 'react'; const useFormData = (editData) => { const [formData, setData] = useState({ ...editData }); const onFormChange = (e) => { setData({ ...formData, [e.target.name]: e.target.value, }); }; const onCheckBoxChange = (e) => { setData((prevData) => ({ ...prevData, [e.target.name]: !prevData[e.target.name], })); } const setFormData = (newData) => { setData({...newData}); } return { formData, onFormChange, onCheckBoxChange, setFormData }; }; export default useFormData; }}} Во секој формулар каде што имаме кориснички влез потребно е да се справиме со него и да го проследиме до серверот во утврдената форма. Ова може да се постигне на стариот добар начин, со користење на концептот useState од React и посебна состојба за секое од input полињата. За да го поедноставиме ова, успешно имплементиравме општа функција за справување со корисничкиот влез која работи на следниот начин. Наместо посебни состојби за секое поле, чуваме состојба на еден објект, каде клучот е соодветно name атрибутот на полето, а вредноста е неговиот value. За процесирање на текстуалните полиња, при промена на вредноста одговорна е функцијата onFormChange, каде се менува состојбата на записот од објектот кој е засегнат според name атрибутот. Оваа функција се извршува на секој onChange настан испален од било која input компонента. onCheckBoxChange е ништо повеќе од специјална имплементација на onFormChange функцијата за checkbox input. Како и во претходно наведените Hook-и, и тука, референци до функциите и состојбите кои треба да бидат пристапени од другите компоненти се враќаат од функцијата. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// == '''Имплементација''' == Се најавува на системот [[Image(login.png)]] == Се регистрира на системот [[Image(registartion.png)]] == Корисник со основни привилегии === Листа сместувања {{{#!java public List getRoomsAvailibilityByDateAndLocation(String hotelLocation, Date dateFrom, Date dateTo, int numberOfBeds, Boolean flexible) { long numberOfNights = Duration.between(dateFrom.toInstant(), dateTo.toInstant()).toDays(); List roomsAvailible = hotelDao.getRoomsAvailibilityByDateAndLocation(hotelLocation, dateFrom, dateTo, numberOfBeds, flexible); Map> roomsByHotels = roomsAvailible.stream().collect(Collectors.groupingBy(x -> x.getHotelRoom().getHotel())); List hotelsList = roomsByHotels.keySet().stream() .map(x -> new HotelDto( x.getHotelId(), x.getHotelName(), x.getHotelDescripiton(), x.getHotelLocation(), x.getHotelEDBS(), x.getParking(), x.getPetFriendly(), x.getInternetAvailable(), roomsByHotels.get(x).stream().mapToDouble(y -> y.getHotelRoom().getPrice()).min().getAsDouble() * numberOfNights, roomsByHotels.get(x), getReviewsForHotel(x.getHotelId()), getReviewsForHotel(x.getHotelId()).stream().mapToDouble(Reviews::getNumStar).average().orElse(0), getHotelImages(x.getHotelId()) )).toList(); return hotelsList; } }}} Во сервисот за да не ги користиме сите податоци од вистинската класа користиме класа HotelDto односно data transfer object. Со ова овозможуваме да за пренос на податоци меѓу backend и frontend. Во функцијата ги листаме сите слободни соби од сите хотели со наведените криетриуми. {{{#!java @GetMapping(path = "/hotel/search") public List searchAvailibleRooms(@RequestParam(name = "hotelLocation") String hotelLocation, @RequestParam(name = "dateFrom") @DateTimeFormat(pattern = "yyyy-MM-dd") Date dateFrom, @RequestParam(name = "dateTo") @DateTimeFormat(pattern = "yyyy-MM-dd") Date dateTo, @RequestParam(name = "numBeds") int numBeds, @RequestParam(name = "flexible") Boolean flexible) { return hotelManager.getRoomsAvailibilityByDateAndLocation(hotelLocation, dateFrom, dateTo, numBeds, flexible); } }}} Контролерот ги прима потребните податоци и ја повикува функцијата од сервисот. ---- [[Image(SearchHotel.png)]] === Листа ресторани [[Image(RestaurantSearch.png)]] === Листа превоз [[Image(TransportSearch.png)]] == Најавен давател на услуги === Регистра бизнис [[Image(registerBusiness.png)]] ---- [[Image(RegisterBusinessForm.png)]] === Додава хотел ---- [[Image(MyResources1.png)]] ---- [[Image(MyResourcesHotel.png)]] ---- [[Image(myResourcesHotel1.png)]] ==== Додава соба во хотел '''За секој хотел се додаваат различни типови на соби, во нашиот случај се додава соба 'Single Room' и од таков тип хотелот има 10 соби. По креирање на собата може истата да се измени, и да се види, од кој до кој датум колку слободни соби има од конкретниот тип.''' ---- [[Image(MyResourcesHotelRooms.png)]] ---- [[Image(HotelRoomAdd.png)]] ---- [[Image(HotelRoom.png)]] ---- [[Image(HotelRoomFreeDates.png)]] ---- [[Image(HotelRoomEdit.png)]] ---- '''Покрај додавање на соби има и опција да се листаат резервациите кои се резервирани од страна на корисници заедно со нивните детали.''' ---- [[Image(HotelReservation.png)]] === Менува хотел '''За секој хотел што го имаме може да пристапиме до неговите информации и да ги измениме по потреба.''' ---- [[Image(MyResourcesHotelEdit.png)]] === Додава ресторан '''При додавање на ресторан, има форма во која може да се внесат детали за ресторанот. За него може да се додаваат слики што би се листале при разгледување на истиот од страна на корисник''' ---- [[Image(MyResourcesRestaurant.png)]] ---- [[Image(MyResourcesRestaurantAdd.png)]] === Менува ресторан '''Секој корисник кој поседува ресторан ја има опцијата на истиот да прави измени''' ---- [[Image(RestaurantEdit.png)]] ==== Додава мени '''За секој ресторан се додаваат различни оброци во менито. Менито може да се изменува, да се додаваат нови и да се менуваат стари ставки. За истите може да се додаваат слики кои корисниците ќе може да ги гледаат во деталите за ресторанот.''' ---- [[Image(RestaurantMenuAdd.png)]] ---- [[Image(RestaurantMenu.png)]] ---- [[Image(RestaurantMenuEdit.png)]] ==== Додава маси '''Покрај оброци се додаваат и маси кои ги поседува ресторанот. За масата се знае колку луѓе може да има на неа и истата може да се изменува и да се види достапност од-до во табелата.''' ---- [[Image(RestaurantTablesAdd.png)]] ---- [[Image(RestaurantTables.png)]] ---- [[Image(RestaurantTableAvailable.png)]] ---- [[Image(RestaurantTableEdit.png)]] ---- '''Исто како и кај хотел така и тука може да се прегледуваат резервациите за самиот ресторан.''' ---- [[Image(ReservationTable.png)]] === Додава транспорт '''При додавање на транспорт, има форма во која може да се внесат детали за транпосртот.''' ---- [[Image(MyResourcesTransport.png)]] ---- [[Image(MyResourcesTranposrtAdd.png)]] ---- [[Image(MyResourcesTransport1.png)]] ---- '''Истиот тој транспорт може да се изменува по потреба''' ---- [[Image(transportEdit.png)]] ==== Додава рути '''Секој транпосрт може да нуди различни рути, од кој до кој град патува патува и попатните градови низ кој поминува. За истите се додава и цена на чинење за секоја релација''' ---- [[Image(RouteAdd.png)]] ---- [[Image(RoutesAddExtended.png)]] ---- [[Image(route.png)]] ---- '''Покрај додавањето има опција и да се прегледуваат резервациите кои ги има одреден транспорт''' ---- [[Image(RouteReservation.png)]] == Најавен администратор '''Администраторот има улога да ги управува профилите, односно да ги одборува, а истото важи и за фирмите.''' === Управување со профили [[Image(profiles4.png)]] === Управува со фирми [[Image(profiles5.png)]] == Најавен корисник на услуги '''Откога корисникот ќе се реши за некоја понуда, тој може да ги види деталите за истата. При резервација на услугата се добива порака за успешна резервација. ''' === Резеревација ресторан [[Image(RestaurantDetails1-min.png)]] ---- [[Image(RestaurantDetails2.png)]] ---- [[Image(RestaurantReservation.png)]] === Резервира транспорт [[Image(TransportDetails.png)]] ---- [[Image(TransportDetails2.png)]] ---- [[Image(TransportReservation.png)]] === Резервира хотел [[Image(HotelDetails1-min.png)]] ---- [[Image(HotelReservationUser-min.png)]] === Откажува услуга '''Корисникот кој ги закажува резервациите има можност да ги прегледува истите. Има резервации кои се активни, односно што следат, но има и претходни резервации за кои може да остави некакво мислење''' ---- [[Image(ReservationsHotel.png)]]