Commit 7929655d authored by Filip Hujer's avatar Filip Hujer Committed by cizek
Browse files

Add stand table for event

parent ede09b19
package cz.fi.muni.pa165.flea.market.manager.controllers;
import static cz.fi.muni.pa165.flea.market.manager.config.PathsConfig.STAND;
import cz.fi.muni.pa165.flea.market.manager.dto.enums.EnumDTO;
import cz.fi.muni.pa165.flea.market.manager.dto.stand.StandCreateDTO;
import cz.fi.muni.pa165.flea.market.manager.dto.stand.StandDTO;
......@@ -14,15 +15,21 @@ import cz.fi.muni.pa165.flea.market.manager.service.StandService;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.dozer.Mapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import static cz.fi.muni.pa165.flea.market.manager.config.PathsConfig.STAND;
/**
* Controller for the stand resource
*
......@@ -65,6 +72,14 @@ public class StandController {
return stands.stream().map(stand -> mapper.map(stand, StandDTO.class)).collect(Collectors.toList());
}
@GetMapping(value = "/list/events/{eventId}")
public List<StandDTO> listByEventId(@PathVariable String eventId) {
List<Stand> stands;
stands = standService.findAllByEventId(eventId);
return stands.stream().map(standService::constructDTOFromStand).collect(Collectors.toList());
}
@GetMapping(value = "/list/category/{categoryId}")
public List<StandDTO> listByItemCategory(@PathVariable String categoryId) {
List<Stand> stands = standService.findAllByItemCategory(categoryId);
......
......@@ -40,6 +40,14 @@ public class StandDao extends DomainDaoImpl<Stand, QStand> {
.list(qEntity);
}
public List<Stand> findAllByEventId(@NonNull String eventId) {
JPAQuery query = new JPAQuery(entityManager);
return query
.from(qEntity)
.where(qEntity.event.id.eq(eventId))
.list(qEntity);
}
public List<Stand> findAllInPriceRange(@NonNull Long fromStandPrice, Long toStandPrice) {
JPAQuery query = new JPAQuery(entityManager);
......
......@@ -7,7 +7,7 @@ import lombok.Setter;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.List;
import java.util.Set;
/**
* @author Filip Hujer
......@@ -19,7 +19,7 @@ public class StandDTO {
@NotBlank
private String id;
private List<String> itemCategories;
private Set<String> itemCategories;
@NotNull
private StandTypeDTO standType;
......
......@@ -3,6 +3,7 @@ package cz.fi.muni.pa165.flea.market.manager.service;
import cz.fi.muni.pa165.flea.market.manager.dao.MarketEventDao;
import cz.fi.muni.pa165.flea.market.manager.dao.StandDao;
import cz.fi.muni.pa165.flea.market.manager.dto.stand.StandCreateDTO;
import cz.fi.muni.pa165.flea.market.manager.dto.stand.StandDTO;
import cz.fi.muni.pa165.flea.market.manager.dto.stand.StandUpdateDTO;
import cz.fi.muni.pa165.flea.market.manager.entity.Stand;
import cz.fi.muni.pa165.flea.market.manager.enums.ItemCategory;
......@@ -16,7 +17,6 @@ import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
/**
......@@ -31,15 +31,15 @@ public class StandService extends DomainServiceImpl<Stand> {
private final StandDao dao;
private final MarketEventDao marketEventDao;
private final StandTypeService standTypeServiceService;
private final StandTypeService standTypeService;
private final UserService userService;
@Autowired
public StandService(StandDao dao, MarketEventDao marketEventDao, StandTypeService standTypeServiceService, UserService userService) {
public StandService(StandDao dao, MarketEventDao marketEventDao, StandTypeService standTypeService, UserService userService) {
super(dao);
this.dao = dao;
this.marketEventDao = marketEventDao;
this.standTypeServiceService = standTypeServiceService;
this.standTypeService = standTypeService;
this.userService = userService;
}
......@@ -63,6 +63,14 @@ public class StandService extends DomainServiceImpl<Stand> {
}
}
public List<Stand> findAllByEventId(@NonNull String eventId) {
try {
return dao.findAllByEventId(eventId);
} catch (Exception e) {
throw new DaoException(e.getMessage(), e);
}
}
public List<Stand> findAllInPriceRange(@NonNull Long fromStandPrice, Long toStandPrice) {
if (toStandPrice != null && toStandPrice < fromStandPrice) {
throw new IllegalArgumentException("To price cannot be bigger than from price");
......@@ -79,7 +87,7 @@ public class StandService extends DomainServiceImpl<Stand> {
Stand stand = new Stand();
stand.setEvent(marketEventDao.findById(standCreateDTO.getEventId()));
stand.setStandType(standTypeServiceService.findById(standCreateDTO.getStandType().getId()));
stand.setStandType(standTypeService.findById(standCreateDTO.getStandType().getId()));
return stand;
}
......@@ -88,22 +96,38 @@ public class StandService extends DomainServiceImpl<Stand> {
Stand stand = new Stand();
stand.setId(id);
stand.setItemCategories(standUpdateDTO.getItemCategories()
.stream()
.map(ItemCategory::valueOf)
.collect(Collectors.toSet()));
if(standUpdateDTO.getItemCategories() != null)
stand.setItemCategories(standUpdateDTO.getItemCategories()
.stream()
.map(ItemCategory::valueOf)
.collect(Collectors.toSet()));
if (standUpdateDTO.getSeller() != null) {
stand.setSeller(userService.findById(standUpdateDTO.getSeller().getId()));
} else {
stand.setSeller(null);
}
stand.setStandType(standTypeServiceService.findById(standUpdateDTO.getStandType().getId()));
stand.setEvent(marketEventDao.findById(standUpdateDTO.getEventId()));
stand.setStandType(standTypeService.findById(standUpdateDTO.getStandType().getId()));
return stand;
}
public StandDTO constructDTOFromStand(@NonNull Stand stand) {
StandDTO standDTO = new StandDTO();
standDTO.setId(stand.getId());
standDTO.setStandType(standTypeService.constructDTOFromStandType(stand.getStandType()));
standDTO.setEventId(stand.getEvent().getId());
if(stand.getSeller() != null)
standDTO.setSeller(userService.constructDTOFromUser(stand.getSeller()));
if(!stand.getItemCategories().isEmpty())
standDTO.setItemCategories(stand.getItemCategories().stream().map(Enum::name).collect(Collectors.toSet()));
return standDTO;
}
@Override
public Stand update(@NonNull Stand stand) {
Stand persistedStand = dao.findById(stand.getId());
......
......@@ -3,6 +3,7 @@ package cz.fi.muni.pa165.flea.market.manager.service;
import cz.fi.muni.pa165.flea.market.manager.dao.StandDao;
import cz.fi.muni.pa165.flea.market.manager.dao.StandTypeDao;
import cz.fi.muni.pa165.flea.market.manager.dto.standType.StandTypeCreateDTO;
import cz.fi.muni.pa165.flea.market.manager.dto.standType.StandTypeDTO;
import cz.fi.muni.pa165.flea.market.manager.dto.standType.StandTypeUpdateDTO;
import cz.fi.muni.pa165.flea.market.manager.entity.Stand;
import cz.fi.muni.pa165.flea.market.manager.entity.StandType;
......@@ -79,4 +80,14 @@ public class StandTypeService extends DomainServiceImpl<StandType> {
return standType;
}
public StandTypeDTO constructDTOFromStandType(@NonNull StandType standType) {
StandTypeDTO typeDTO = new StandTypeDTO();
typeDTO.setId(standType.getId());
typeDTO.setName(standType.getName());
typeDTO.setPriceInCents(standType.getPriceInCents());
return typeDTO;
}
}
......@@ -2,6 +2,7 @@ package cz.fi.muni.pa165.flea.market.manager.service;
import cz.fi.muni.pa165.flea.market.manager.dao.UserDao;
import cz.fi.muni.pa165.flea.market.manager.dto.user.UserCreateDTO;
import cz.fi.muni.pa165.flea.market.manager.dto.user.UserDTO;
import cz.fi.muni.pa165.flea.market.manager.dto.user.UserUpdateDTO;
import cz.fi.muni.pa165.flea.market.manager.entity.User;
import cz.fi.muni.pa165.flea.market.manager.enums.UserRole;
......@@ -35,7 +36,7 @@ public class UserService extends DomainServiceImpl<User> {
public User findByEmail(@NonNull String email) {
try {
return dao.findByEmail(email);
} catch (Exception e){
} catch (Exception e) {
throw new DaoException(e.getMessage(), e);
}
}
......@@ -62,8 +63,16 @@ public class UserService extends DomainServiceImpl<User> {
return user;
}
public UserDTO constructDTOFromUser(User user) {
UserDTO userDTO = new UserDTO();
userDTO.setEmail(user.getEmail());
userDTO.setUserRole(user.getUserRole());
return userDTO;
}
public void mergeUpdateUserFromDTO(User user, UserUpdateDTO userUpdateDTO) {
// TODO: find if theres a better way to merge objects (maybe with dozer)
if (userUpdateDTO.getEmail() != null) {
user.setEmail(userUpdateDTO.getEmail());
}
......
import {useCallback, useEffect, useMemo, useState} from "react";
import {Form, Formik} from "formik";
import {useMutation, useQueryClient} from "react-query";
import {Button, Group, Select, Stack, Title} from "@mantine/core";
import {DialogProps} from "context/dialog/types";
import {useNotification} from "context/notification/notification-context";
import {createStand, editStand} from "modules/event/create-event-api";
import {EventStand, EventStandForm} from "../../../modules/event/event-types";
import {getStandTypes} from "../../../modules/stand-reservation/stand-reservation-api";
import {StandType} from "../../../models/stand-type";
type FormValues = Omit<EventStandForm, "id">;
interface Props {
eventId: string;
stand?: EventStand;
}
export const UpsertEventStandDialog = ({
stand,
eventId,
close,
}: DialogProps<Props>) => {
const queryClient = useQueryClient();
const { showNotification } = useNotification();
const [standTypes, setStandTypes] = useState<StandType[]>([]);
useEffect(() => {
getStandTypes(eventId).then((data) => {
const parsedData = data.map((standType: StandType) => ({
name: standType.name,
id: standType.id,
}));
setStandTypes(parsedData);
});
}, [eventId]);
const { mutate: edit, isLoading: editLoading } = useMutation(editStand, {
onError: () => {
showNotification({
content: "Editing event stand failed",
variant: "error",
});
},
onSuccess: () => {
showNotification({
content: "Stand succesfully edited",
variant: "success",
});
queryClient.invalidateQueries("eventStands");
close();
},
});
const { mutate: create, isLoading: createLoading } = useMutation(
createStand,
{
onError: () => {
showNotification({
content: "Adding stand failed",
variant: "error",
});
},
onSuccess: () => {
showNotification({
content: "Stand succesfully added",
variant: "success",
});
queryClient.invalidateQueries("eventStands");
close();
},
}
);
const handleSubmit = useCallback(
(values: FormValues) => {
const selectedStandType = standTypes.find((standType) => {
return standType.id == values.standTypeId;
});
if (selectedStandType != undefined)
stand
? edit({ id: stand.id, standType: selectedStandType, eventId: eventId })
: create({ standType: selectedStandType, eventId: eventId });
},
[create, edit, eventId, stand, standTypes]
);
const initialValues = useMemo<FormValues>(() => {
if (!stand) {
return {
standTypeId: "",
};
}
const { standType } = stand;
return { standTypeId: standType.id };
}, [stand]);
const isLoading = useMemo(
() => editLoading || createLoading,
[editLoading, createLoading]
);
return (
<Formik initialValues={initialValues} onSubmit={handleSubmit}>
{({ setFieldValue, errors }) => (
<Form>
<Stack>
<Select
name="standTypeId"
onChange={(value) => {
setFieldValue("standTypeId", value);
}}
label="Select Stand Type"
placeholder="Pick one"
data={standTypes.map((standType) => ({
value: standType.id,
label: standType.name,
}))}
error={errors.standTypeId}
required
/>
<Group position="right">
<Button type="submit" variant="gradient" loading={isLoading}>
Submit
</Button>
<Button
variant="subtle"
color="red"
disabled={isLoading}
onClick={close}
>
Cancel
</Button>
</Group>
</Stack>
</Form>
)}
</Formik>
);
};
......@@ -14,6 +14,7 @@ import { Register } from "./register/register";
import { Events } from "./events/events";
import { CreateUpdateEvent } from "./event/create-update-event";
import { Locations } from "./locations/locations";
import {EventStands} from "./event/event-stands";
import { UserReservations } from "./stand-reservation/user-reservations";
export const AppRoutes = () => {
......@@ -36,13 +37,14 @@ export const AppRoutes = () => {
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
</Route>
<Route path="/events" element={<Events />} />
<Route path="/events/create" element={<CreateUpdateEvent />} />
<Route path="/events/:id/edit" element={<CreateUpdateEvent />} />
<Route element={<ProtectedRoute />}>
<Route path="/locations" element={<Locations />} />
</Route>
<Route path="*" element={<NotFound />} />
<Route path="/events" element={<Events />} />
<Route path="/stands/list/events/:id" element={<EventStands />} />
<Route
path="/stand-reservation/:id"
element={<StandReservation />}
......
import axios from "axios";
import { apiUrl } from "constants/api-url";
import { CreateEventValues, UpdateEventEntryVariables } from "./event-types";
import {
CreateEventValues,
EventStand,
EventStandAction,
UpdateEventEntryVariables,
} from "./event-types";
import { MarketEvent, MarketLocation } from "../../models";
export const postCreateEvent = (values: CreateEventValues) =>
......@@ -23,3 +28,23 @@ export const putUpdateEvent = ({
values,
}: UpdateEventEntryVariables) =>
axios.put(`${apiUrl}/market-events/${eventId}`, values);
export const getEventStands = (eventId: string) => {
return axios
.get<EventStand[]>(`${apiUrl}/stands/list/events/${eventId}`)
.then(({ data }) => data);
}
export const deleteStand = (id: string) =>
axios.delete<unknown>(`${apiUrl}/stands/${id}`).then(({ data }) => data);
export const editStand = (stand: EventStandAction) => {
const { id, ...rest } = stand;
return axios
.put<unknown>(`${apiUrl}/stands/${id}`, rest)
.then(({ data }) => data);
};
export const createStand = (stand: Omit<EventStandAction, "id">) =>
axios.post<unknown>(`${apiUrl}/stands`, stand).then(({ data }) => data);
import { useCallback } from "react";
import { ActionIcon, Group } from "@mantine/core";
import { Edit, Trash } from "tabler-icons-react";
import { TableCellProps } from "components/table/types";
import { useMutation, useQueryClient } from "react-query";
import { useNotification } from "context/notification/notification-context";
import { useDialog } from "context/dialog/dialog-context";
import { deleteStand } from "./create-event-api";
import { UpsertEventStandDialog } from "../../components/dialogs/upsert-event-stand-dialog/upsert-event-stand-dialog";
import { EventStand } from "./event-types";
import { ConfirmDialog } from "../../components/dialogs/confirm-dialog/confirm-dialog";
export const EventStandActionsCell = ({ row }: TableCellProps<EventStand>) => {
const { showNotification } = useNotification();
const { open } = useDialog();
const queryClient = useQueryClient();
const deleteMutation = useMutation(deleteStand, {
onError: () => {
showNotification({
content: "Deleting event stand failed",
variant: "error",
});
},
onSuccess: () => {
showNotification({
content: "Stand deleted",
variant: "success",
});
},
onSettled: () => {
queryClient.invalidateQueries("eventStands");
},
});
const handleEdit = useCallback(() => {
open({
Content: UpsertEventStandDialog,
props: {eventId: row.eventId, stand: row },
title: "Edit stand",
});
}, [open, row]);
const handleDelete = useCallback(() => {
deleteMutation.mutate(row.id);
}, [deleteMutation, row.id]);
const openConfirmDialog = useCallback(
() => {
open({
Content: ConfirmDialog,
props: {
onConfirm: () => {
handleDelete();
},
onCancel: () => {
showNotification({
content: "Stand not deleted",
variant: "info",
});
},
prompt: "Are you sure you want to delete this stahd?",
},
title: "Delete stand",
});
},
[open, handleDelete, showNotification]
);
return (
<Group spacing="xs">
<ActionIcon variant="hover" onClick={handleEdit}>
<Edit />
</ActionIcon>
<ActionIcon variant="hover" onClick={openConfirmDialog}>
<Trash color="red" />
</ActionIcon>
</Group>
);
};
import { useCallback } from "react";
import { Button, Group } from "@mantine/core";
import { Plus } from "tabler-icons-react";
import { useDialog } from "context/dialog/dialog-context";
import { UpsertEventStandDialog } from "../../components/dialogs/upsert-event-stand-dialog/upsert-event-stand-dialog";
export const eventStandsActionFactory = (eventId: string) =>
function EventStandsAction() {
const { open } = useDialog();
const openAddEventStandDialog = useCallback(() => {
open({
Content: UpsertEventStandDialog,
props: { eventId },
title: "Add stand",
});
}, [open]);
return (
<Group>
<Button
variant="outline"
leftIcon={<Plus />}
onClick={openAddEventStandDialog}
>
Add stand
</Button>
</Group>
);