wiki:UseCaseImplementations

Version 5 (modified by 211255, 4 months ago) ( diff )

--

Имплементација на кориснички сценарија

Општи информации

Нашата апликација, како што е наведено и во претходните фази, се темели на клиент-сервер архитектура. Во овој случај клиентската страна е „претставена“ преку React апликација, додека пак серверската страна е Spring Boot апликација. Во основа на оваа архитектура е комуникација меѓу клиентот и серверот преку HTTP барања. За визуелизација и давање можност за интеракција на крајниот корисник, кој управува директно со апликацијата на клиентска страна, беше потребно да воспоставуваме конекција и соодветно да ги обработуваме простите податоци кои пристигнуваат од серверската страна. Ова го постигнавме преку Axios HTTP клиентот кој е инсталиран на клиенстката апликација и неговата инстанца во продолжение е онаа преку која ги испраќаме сите барања до серверот.

Error: Failed to load processor javascript
No macro or processor named 'javascript' found

На овој начин обезбедуваме при секое барање основниот URL да биде основниот URL на серверската апликација, додека пак со поставување на параметарот withCredentials овозможуваме препраќање на колачињата и XMLHttpRequest, што овозможува прибавување на нови податоци без целосно одновно вчитување на страницата.

Custom React Hook-и

За поедноставно справување со HTTP GET и POST барањата, кои како што претходно споменавме се главната врска меѓу клиентската и серверската страна и практично не е можно да постои компонента каде не се употребуваат, креиравме наши React Hook-и кои се реискористливи и можат да се употребуваат во различни сценарија со едноставна промена на URL-то

useGet

Error: Failed to load processor javascript
No macro or processor named 'javascript' found

Како што и самото име кажува, овој 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

Error: Failed to load processor javascript
No macro or processor named 'javascript' found

Овој Hook го користиме за POST барања кон серверската страна. Од него постојат неколку модифицирани верзии во зависоност од тоа дали парамтерите се испраќаат во телото на барањето, како URL параметри или како променливи во патеката, но во основа се работи за иста имплементација. За разлика од useGet, тука, URL-то се испраќа како аргумент на функцијата, заедно со податоците кои треба да се испратат како параметри и состојбата на зависната променлива за која зборувавме во претходната компонент. Откако ќе се испрати POST, se враќа Promise со кој соодветно се справуваме на горенаведениот начин, со единствна разлика што во then сегментот, кога статусот означува успех, ја менуваме состојбата на зависната променлива за да се ререндерираат зависните компоненти.

useFormData

Error: Failed to load processor javascript
No macro or processor named 'javascript' found

Во секој формулар каде што имаме кориснички влез потребно е да се справиме со него и да го проследиме до серверот во утврдената форма. Ова може да се постигне на стариот добар начин, со користење на концептот useState од React и посебна состојба за секое од input полињата. За да го поедноставиме ова, успешно имплементиравме општа функција за справување со корисничкиот влез која работи на следниот начин. Наместо посебни состојби за секое поле, чуваме состојба на еден објект, каде клучот е соодветно name атрибутот на полето, а вредноста е неговиот value. За процесирање на текстуалните полиња, при промена на вредноста одговорна е функцијата onFormChange, каде се менува состојбата на записот од објектот кој е засегнат според name атрибутот. Оваа функција се извршува на секој onChange настан испален од било која input компонента. onCheckBoxChange е ништо повеќе од специјална имплементација на onFormChange функцијата за checkbox input. Како и во претходно наведените Hook-и, и тука, референци до функциите и состојбите кои треба да бидат пристапени од другите компоненти се враќаат од функцијата.

Имплементација

Се најавува на системот

Се регистрира на системот

Корисник со основни привилегии

Листа сместувања

public List<HotelRoomAvailable> getRoomsAvailibilityByDateAndLocation(String hotelLocation, Date dateFrom, Date dateTo, int numberOfBeds, Boolean flexible) {
        if(flexible)
        {
            Calendar calendar = Calendar.getInstance();
            Calendar calendar1 = Calendar.getInstance();
            calendar.setTime(dateTo);
            calendar1.setTime(dateFrom);
            return em.createQuery("SELECT hr FROM HotelRoomAvailable hr WHERE " +
                            "((hr.dateFrom <= :dateTo AND hr.dateTo >= :dateFrom) OR " +
                            "(hr.dateFrom <= :dateToMinus1 AND hr.dateTo >= :dateFromMinus1) OR " +
                            "(hr.dateFrom <= :dateToMinus2 AND hr.dateTo >= :dateFromMinus2) OR " +
                            "(hr.dateFrom <= :dateToMinus3 AND hr.dateTo >= :dateFromMinus3) OR " +
                            "(hr.dateFrom <= :dateToPlus1 AND hr.dateTo >= :dateFromPlus1) OR " +
                            "(hr.dateFrom <= :dateToPlus2 AND hr.dateTo >= :dateFromPlus2) OR " +
                            "(hr.dateFrom <= :dateToPlus3 AND hr.dateTo >= :dateFromPlus3)) " +
                            "AND hr.hotelRoom.hotel.hotelLocation LIKE :hotelLocation " +
                            "AND hr.hotelRoom.numOfBeds >= :numBeds")
                    .setParameter("hotelLocation", hotelLocation)
                    .setParameter("dateFrom", dateFrom)
                    .setParameter("dateTo", dateTo)
                    .setParameter("dateToMinus1", subtractDays(calendar, 1))
                    .setParameter("dateToMinus2", subtractDays(calendar, 2))
                    .setParameter("dateToMinus3", subtractDays(calendar, 3))
                    .setParameter("dateToPlus1", addDays(calendar, 1))
                    .setParameter("dateToPlus2", addDays(calendar, 2))
                    .setParameter("dateToPlus3", addDays(calendar, 3))
                    .setParameter("dateFromMinus1", subtractDays(calendar1, 1))
                    .setParameter("dateFromMinus2", subtractDays(calendar1, 2))
                    .setParameter("dateFromMinus3", subtractDays(calendar1, 3))
                    .setParameter("dateFromPlus1", addDays(calendar1, 1))
                    .setParameter("dateFromPlus2", addDays(calendar1, 2))
                    .setParameter("dateFromPlus3", addDays(calendar1, 3))
                    .setParameter("numBeds", numberOfBeds)
                    .getResultList();
        }
        else
        {
            return em.createQuery("SELECT hr FROM HotelRoomAvailable hr WHERE " +
                            "((hr.dateFrom <= :dateTo AND hr.dateTo >= :dateFrom)) " +
                            "AND hr.hotelRoom.hotel.hotelLocation LIKE :hotelLocation " +
                            "AND hr.hotelRoom.numOfBeds >= :numBeds")
                    .setParameter("hotelLocation", hotelLocation)
                    .setParameter("dateFrom", dateFrom)
                    .setParameter("dateTo", dateTo)
                    .setParameter("numBeds", numberOfBeds)
                    .getResultList();
        }
    }

Ова е кверито од кое се зимаат хотелите по криетриум, каде што првиот дел е додека флексибилноста од 3 дена има вредност true, па тогаш се листаат +/- (1,2,3) дена од бараните критериуми, а доколку има вредност false тогаш се листаат според точните дати кои се внесени.

public List<HotelDto> getRoomsAvailibilityByDateAndLocation(String hotelLocation, Date dateFrom, Date dateTo, int numberOfBeds, Boolean flexible) {
        long numberOfNights = Duration.between(dateFrom.toInstant(), dateTo.toInstant()).toDays();
        List<HotelRoomAvailable> roomsAvailible = hotelDao.getRoomsAvailibilityByDateAndLocation(hotelLocation, dateFrom, dateTo, numberOfBeds, flexible);
        Map<Hotels, List<HotelRoomAvailable>> roomsByHotels = roomsAvailible.stream().collect(Collectors.groupingBy(x -> x.getHotelRoom().getHotel()));
        List<HotelDto> 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. Во функцијата ги листаме сите слободни соби од сите хотели со наведените криетриуми.

@GetMapping(path = "/hotel/search")
    public List<HotelDto> 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);
    }

Контролерот ги прима потребните податоци и ја повикува функцијата од сервисот.

Листа ресторани

public List<RestaurantsAvailible> getTablesByDateAndLocation(String restaurantLocation, Date hourFrom, Date hourTo, int noSeats){
        return em.createQuery("select hr from RestaurantsAvailible hr where hr.hourFrom <= :hourFrom and hr.hourTo >= :hourTo " +
                        "and hr.restaurantTable.restaurant.restaurantLocation LIKE :restaurantLocation and hr.restaurantTable.noSeats >= :noSeats")
                .setParameter("restaurantLocation", restaurantLocation)
                .setParameter("hourFrom", hourFrom)
                .setParameter("hourTo", hourTo)
                .setParameter("noSeats", noSeats)
                .getResultList();
    }

Ова е кверито кое ги листа сите маси во одредена локација, со одредено време на доаѓање, заминување и бројка колку луѓе ќе дојдат.

public List<RestaurantDto> getTablesByDateAndLocation(String restaurantLocation, Date date, String hourFrom, String hourTo, int noSeats) {
        Date dateFrom = date;
        Date dateTo = Date.from(date.toInstant());
        String[] splittedFrom = hourFrom.split(":");
        String[] splittedTo = hourTo.split(":");
        dateFrom.setHours(Integer.parseInt(splittedFrom[0]));
        dateFrom.setMinutes(Integer.parseInt(splittedFrom[1]));
        dateTo.setHours(Integer.parseInt(splittedTo[0]));
        dateTo.setMinutes(Integer.parseInt(splittedTo[1]));
        
        List<RestaurantsAvailible> restaurantsAvailibles = restaurantDao.getTablesByDateAndLocation(restaurantLocation, hourFrom, hourTo, noSeats);
        Map<Restaurant, List<RestaurantsAvailible>> tablesByRestaurants = restaurantsAvailibles.stream().collect(Collectors.groupingBy(x -> x.getRestaurantTable().getRestaurant()));
        List<RestaurantDto> restaurantsList = tablesByRestaurants.keySet().stream()
                .map(x -> new RestaurantDto(
                        x.getRestaurantID(),
                        x.getRestaurantName(),
                        x.getRestaurantLocation(),
                        x.getCousineType(),
                        x.getRestaurantDescription(),
                        x.getRestaurantEdbs(),
                        x.getMenus(),
                        tablesByRestaurants.get(x),
                        getReviewsForRestaurant(x.getRestaurantID()),
                        getReviewsForRestaurant(x.getRestaurantID()).stream().mapToDouble(Reviews::getNumStar).average().orElse(0),
                        getRestaurantImages(x.getRestaurantID()),
                        getMenuImagesByRestaurant(x.getRestaurantID())
                )).toList();
        return restaurantsList;
    }

Сервисот кој исто како и во хотели користи Dto притоа ги листа сите ресторани кои можат да го опслужат барањето од корисниците. Со тоа што времето од-до се интерпретира како текст, па тие ги средуваме во сервисот.

@GetMapping(path = "/restaurant/search")
    public List<RestaurantDto> searchAvailableRestaurant(@RequestParam(name = "restaurantLocation") String restaurantLocation,
                                                         @RequestParam(name = "date") @DateTimeFormat(pattern = "yyyy-MM-dd") Date date,
                                                         @RequestParam(name = "hourFrom") String hourFrom,
                                                         @RequestParam(name = "hourTo") String hourTo,
                                                         @RequestParam(name = "numPeople") int noSeats) {

        return restaurantManager.getTablesByDateAndLocation(restaurantLocation, date, dateFrom, dateTo, noSeats);
    }

Во контролерот се примаат податоци за локација, датум, време од до во форма на текст и број на седишта

Листа превоз

public List<TransportRoute> getTransportsAvailableByFilters(String fromL, String toL, Date date, int numPassengers) {
        return em.createQuery("select h from TransportRoute h where h.from = :froml and h.to = :tol and h.freeSpace >= :nump")
                .setParameter("froml", fromL)
                .setParameter("tol", toL)
                .setParameter("nump", numPassengers)
                .getResultList();
    }

Ова е кверито со кое се наоѓаат сите слобдни транспорти со одредена дата од-до одредено место и број на патници

public List<TransportListingDto> getTransportsAvailableByFilters(String from, String to, Date date, int numPassengers) {
        List<TransportRoute> transportAvailable = transportDao.getTransportsAvailableByFilters(from, to, date, numPassengers);
        Map<TransportAvailible, List<TransportRoute>> transportsByTransporter = transportAvailable.stream().collect(Collectors.groupingBy(x -> x.getParentRoute()));
        List<TransportListingDto> transportList = transportsByTransporter.keySet().stream().toList().stream()
                .map(x -> new TransportListingDto(
                        x.getTransportAvailibleId(),
                        x.getFrom(),
                        x.getTo(),
                        x.getDate(),
                        x.getFreeSpace(),
                        x.getTime(),
                        transportsByTransporter.get(x).stream().mapToDouble(y -> y.getPrice()).min().getAsDouble(),
                        x.getRoutes(),
                        x.getTransport(),
                        getReviewsForTransport(x.getTransport().getTransportID()),
                        getReviewsForTransport(x.getTransport().getTransportID()).stream().mapToDouble(Reviews::getNumStar).average().orElse(0)
                )).collect(Collectors.toList());
        return transportList;
    }

Исто како и останатите имаме Dto и ги листаме сите слобдони транспорти со наведените критериуми. Притоа што доколку се внесе некој град кој е попатен во некоја рута се листа и таа со тоа што се зима главната рута на тој град.

@GetMapping(path = "/transport/search")
    public List<TransportListingDto> searchAvailableTransport(@RequestParam(name = "from") String from,
                                                              @RequestParam(name = "to") String to,
                                                              @RequestParam(name = "date") @DateTimeFormat(pattern = "yyyy-MM-dd") Date date,
                                                              @RequestParam(name = "numPassengers") int numPassengers){
        return transportManager.getTransportsAvailableByFilters(from, to, date, numPassengers);
    }

Контролерот кој според влезните критериуми ги листаме сите слобдони транспорти за тие дестинации.

Најавен давател на услуги

Регистра бизнис


Додава хотел




Додава соба во хотел

За секој хотел се додаваат различни типови на соби, во нашиот случај се додава соба 'Single Room' и од таков тип хотелот има 10 соби. По креирање на собата може истата да се измени, и да се види, од кој до кој датум колку слободни соби има од конкретниот тип.







Покрај додавање на соби има и опција да се листаат резервациите кои се резервирани од страна на корисници заедно со нивните детали.


Менува хотел

За секој хотел што го имаме може да пристапиме до неговите информации и да ги измениме по потреба.


Додава ресторан

При додавање на ресторан, има форма во која може да се внесат детали за ресторанот. За него може да се додаваат слики што би се листале при разгледување на истиот од страна на корисник



No image "MyResourcesRestaurantAdd.png" attached to UseCaseImplementations

Менува ресторан

Секој корисник кој поседува ресторан ја има опцијата на истиот да прави измени


Додава мени

За секој ресторан се додаваат различни оброци во менито. Менито може да се изменува, да се додаваат нови и да се менуваат стари ставки. За истите може да се додаваат слики кои корисниците ќе може да ги гледаат во деталите за ресторанот.




Додава маси

Покрај оброци се додаваат и маси кои ги поседува ресторанот. За масата се знае колку луѓе може да има на неа и истата може да се изменува и да се види достапност од-до во табелата.






Исто како и кај хотел така и тука може да се прегледуваат резервациите за самиот ресторан.


Додава транспорт

При додавање на транспорт, има форма во која може да се внесат детали за транпосртот.





Истиот тој транспорт може да се изменува по потреба


Додава рути

Секој транпосрт може да нуди различни рути, од кој до кој град патува патува и попатните градови низ кој поминува. За истите се додава и цена на чинење за секоја релација





Покрај додавањето има опција и да се прегледуваат резервациите кои ги има одреден транспорт


Најавен администратор

Администраторот има улога да ги управува профилите, односно да ги одборува, а истото важи и за фирмите.

Управување со профили

Управува со фирми

Најавен корисник на услуги

Откога корисникот ќе се реши за некоја понуда, тој може да ги види деталите за истата. При резервација на услугата се добива порака за успешна резервација.

Резеревација ресторан



Резервира транспорт



Резервира хотел


Откажува услуга

Корисникот кој ги закажува резервациите има можност да ги прегледува истите. Има резервации кои се активни, односно што следат, но има и претходни резервации за кои може да остави некакво мислење


Attachments (53)

Note: See TracWiki for help on using the wiki.