Commit 10e22f38 authored by Vladimir Trubachoff's avatar Vladimir Trubachoff

[WIP] Fixes

parent fd540771
package com.example.testj.domain; package com.example.testj.domain;
import jakarta.persistence.*; import java.math.BigDecimal;
import java.util.List;
import com.example.testj.service.dto.GoodsDto;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import jakarta.validation.constraints.Digits; import jakarta.validation.constraints.Digits;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
import java.util.List;
@Entity @Entity
@Table(name = "goods") @Table(name = "goods")
public class Goods { public class Goods {
...@@ -18,13 +28,27 @@ public class Goods { ...@@ -18,13 +28,27 @@ public class Goods {
@Column(name = "name") @Column(name = "name")
private String name; private String name;
@Digits
@NotEmpty @NotEmpty
@Column(name = "price") @Column(name = "price")
private Number price; private BigDecimal price;
// @JsonIgnore
// @OneToMany(mappedBy= "goods", cascade = CascadeType.ALL, orphanRemoval = true)
// private List<OrderLine> orderLine;
public Goods() {
}
@OneToMany(mappedBy = "goods") public Goods(Long id, String name, BigDecimal price) {
List<OrderLine> orderLines; this.id = id;
this.name = name;
this.price = price;
}
public Goods(Goods goods) {
this.name = goods.getName();
this.price = goods.getPrice();
}
public Long getId() { public Long getId() {
return id; return id;
...@@ -42,11 +66,19 @@ public class Goods { ...@@ -42,11 +66,19 @@ public class Goods {
this.name = name; this.name = name;
} }
public Number getPrice() { public BigDecimal getPrice() {
return price; return price;
} }
public void setPrice(Number price) { public void setPrice(BigDecimal price) {
this.price = price; this.price = price;
} }
// public List<OrderLine> getOrderLine() {
// return orderLine;
// }
// public void setOrderLine(List<OrderLine> orderLine) {
// this.orderLine = orderLine;
// }
} }
package com.example.testj.domain; package com.example.testj.domain;
import jakarta.persistence.*; import java.util.List;
import jakarta.validation.constraints.NotBlank;
import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat;
import java.util.List; import com.fasterxml.jackson.annotation.JsonManagedReference;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotBlank;
@Entity @Entity
@Table(name = "orders") @Table(name = "orders")
...@@ -26,7 +36,7 @@ public class Order { ...@@ -26,7 +36,7 @@ public class Order {
@Column(name = "address") @Column(name = "address")
private String address; private String address;
@OneToMany(mappedBy = "order", fetch = FetchType.LAZY, cascade = CascadeType.ALL) @OneToMany(mappedBy = "order", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
List<OrderLine> orderLines; List<OrderLine> orderLines;
public Order() { public Order() {
...@@ -38,10 +48,23 @@ public class Order { ...@@ -38,10 +48,23 @@ public class Order {
this.date = date; this.date = date;
} }
public void addOrderLine(OrderLine orderLine) {
orderLines.add(orderLine);
orderLine.setOrder(this);
}
public void removeOrderLine(OrderLine orderLine) {
orderLines.remove(orderLine);
}
public List<OrderLine> getOrderLines() { public List<OrderLine> getOrderLines() {
return orderLines; return orderLines;
} }
public void setOrderLines(List<OrderLine> orderLines) {
this.orderLines = orderLines;
}
public String getDate() { public String getDate() {
return date; return date;
} }
......
package com.example.testj.domain; package com.example.testj.domain;
import jakarta.persistence.*;
import org.springframework.format.annotation.NumberFormat; import org.springframework.format.annotation.NumberFormat;
import java.util.Set; import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.validation.constraints.Min;
@Entity @Entity
@Table(name = "order_line") @Table(name = "order_line")
...@@ -13,20 +21,20 @@ public class OrderLine { ...@@ -13,20 +21,20 @@ public class OrderLine {
@Column(name = "id") @Column(name = "id")
private Long id; private Long id;
@JsonIgnore
@ManyToOne @ManyToOne
@JoinColumn(name = "order_id", nullable = false) @JoinColumn(name = "order_id")
private Order order; private Order order;
@ManyToOne @ManyToOne
@JoinColumn(name = "goods_id", nullable = false) @JoinColumn(name = "goods_id")
private Goods goods; private Goods goods;
@Column(name = "count") @Column(name = "count")
@NumberFormat @NumberFormat
@Min(1)
private int count; private int count;
public Long getId() { public Long getId() {
return id; return id;
} }
...@@ -46,4 +54,17 @@ public class OrderLine { ...@@ -46,4 +54,17 @@ public class OrderLine {
public Goods getGoods() { public Goods getGoods() {
return goods; return goods;
} }
public void setGoods(Goods goods) {
this.goods = goods;
}
public Order getOrder() {
return order;
}
public void setOrder(Order order) {
this.order = order;
}
} }
...@@ -2,8 +2,9 @@ package com.example.testj.repository; ...@@ -2,8 +2,9 @@ package com.example.testj.repository;
import com.example.testj.domain.Goods; import com.example.testj.domain.Goods;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
@Repository @Repository
public interface GoodsRepository extends CrudRepository<Goods, Long> { public interface GoodsRepository extends PagingAndSortingRepository<Goods, Long>, CrudRepository<Goods, Long> {
} }
\ No newline at end of file
package com.example.testj.service;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PagedModel;
import org.springframework.stereotype.Service;
import com.example.testj.domain.Goods;
import com.example.testj.exception.ResourceNotFoundException;
import com.example.testj.repository.GoodsRepository;
import com.example.testj.service.dto.GoodsDto;
import com.example.testj.utils.MappingUtils;
@Service
public class GoodsService {
private final GoodsRepository goodsRepository;
private final MappingUtils mappingUtils;
public GoodsService(GoodsRepository goodsRepository, MappingUtils mappingUtils) {
this.goodsRepository = goodsRepository;
this.mappingUtils = mappingUtils;
}
public PagedModel<GoodsDto> getAllGoods(Integer page, Integer size, String sortBy, String sortDir) {
return new PagedModel<>(goodsRepository
.findAll(PageRequest.of(page, size, Sort.by(Sort.Direction.fromString(sortDir), sortBy)))
.map(mappingUtils::toGoodsDto));
}
public GoodsDto getGoods(Long id) {
return goodsRepository.findById(id).map(mappingUtils::toGoodsDto).orElse(null);
}
public GoodsDto createOrUpdate(Goods goods) {
return mappingUtils.toGoodsDto(this.goodsRepository.save(goods));
}
public void deleteGoods(Long id) {
this.goodsRepository.findById(id).orElseThrow(ResourceNotFoundException::new);
this.goodsRepository.deleteById(id);
}
}
package com.example.testj.service;
import org.springframework.stereotype.Service;
import com.example.testj.domain.OrderLine;
import com.example.testj.exception.ResourceNotFoundException;
import com.example.testj.repository.OrderLineRepository;
import com.example.testj.service.dto.OrderLineDto;
import com.example.testj.utils.MappingUtils;
@Service
public class OrderLineService {
private final MappingUtils mappingUtils;
private final OrderLineRepository orderLineRepository;
public OrderLineService(OrderLineRepository orderLineRepository, MappingUtils mappingUtils) {
this.orderLineRepository = orderLineRepository;
this.mappingUtils = mappingUtils;
}
public OrderLineDto createOrUpdateOrderLine(OrderLine orderLine) {
return mappingUtils.toOrderLineDto(orderLineRepository.save(orderLine));
}
public OrderLineDto getOrderLine(Long id) throws ResourceNotFoundException {
return mappingUtils.toOrderLineDto(orderLineRepository.findById(id).orElseThrow(ResourceNotFoundException::new));
}
public void deleteOrderLine(Long id) {
orderLineRepository.findById(id).orElseThrow(ResourceNotFoundException::new);
orderLineRepository.deleteById(id);
}
}
package com.example.testj.service; package com.example.testj.service;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PagedModel;
import org.springframework.stereotype.Service;
import com.example.testj.domain.Order; import com.example.testj.domain.Order;
import com.example.testj.dto.OrderDTO;
import com.example.testj.exception.ResourceNotFoundException; import com.example.testj.exception.ResourceNotFoundException;
import com.example.testj.repository.OrderRepository; import com.example.testj.repository.OrderRepository;
import com.example.testj.service.dto.OrderDto;
import com.example.testj.utils.MappingUtils;
import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PagedModel;
import org.springframework.stereotype.Service;
@Service @Service
public class OrderService { public class OrderService {
private final OrderRepository orderRepository; private final OrderRepository orderRepository;
private final MappingUtils mappingUtils;
public OrderService(OrderRepository orderRepository) { public OrderService(OrderRepository orderRepository, MappingUtils mappingUtils) {
this.orderRepository = orderRepository; this.orderRepository = orderRepository;
this.mappingUtils = mappingUtils;
} }
public PagedModel<Order> getOrders(Integer page, Integer size, String sortBy, String sortDir) { public PagedModel<OrderDto> getOrders(Integer page, Integer size, String sortBy, String sortDir) {
return new PagedModel<Order>(orderRepository.findAll(PageRequest.of(page, size, Sort.by(Sort.Direction.fromString(sortDir), sortBy)))); return new PagedModel<>(orderRepository
.findAll(PageRequest.of(page, size, Sort.by(Sort.Direction.fromString(sortDir), sortBy)))
.map(mappingUtils::toOrderDto));
} }
public Order getOrder(Long id) throws ResourceNotFoundException { public OrderDto getOrder(Long id) throws ResourceNotFoundException {
return orderRepository.findById(id).orElseThrow(ResourceNotFoundException::new); return mappingUtils.toOrderDto(orderRepository.findById(id).orElseThrow(ResourceNotFoundException::new));
} }
public Order saveOrUpdateOrder(Order order) { public void deleteOrder(Long id) throws ResourceNotFoundException {
return orderRepository.save(order); orderRepository.findById(id).orElseThrow(ResourceNotFoundException::new);
orderRepository.deleteById(id);
} }
public Order patchOrder(Long id, JsonNode patch) throws JsonMappingException { public OrderDto createOrUpdateOrder(Order order) {
Order orderData = orderRepository.findById(id).orElseThrow(ResourceNotFoundException::new); return mappingUtils.toOrderDto(orderRepository.save(order));
Order patchedOrder = new ObjectMapper().updateValue(orderData, patch);
return orderRepository.save(patchedOrder);
} }
public void deleteOrder(Long id) throws ResourceNotFoundException { public OrderDto patchOrder(Long id, JsonNode patch) throws JsonMappingException {
orderRepository.findById(id).orElseThrow(ResourceNotFoundException::new); Order orderData = orderRepository.findById(id).orElseThrow(ResourceNotFoundException::new);
orderRepository.deleteById(id); Order patchedOrder = new ObjectMapper().updateValue(orderData, patch);
return mappingUtils.toOrderDto(orderRepository.save(patchedOrder));
} }
} }
package com.example.testj.dto; package com.example.testj.service.dto;
import java.io.Serializable; import java.io.Serializable;
import java.math.BigDecimal;
import com.example.testj.domain.Goods;
/** /**
* DTO for {@link com.example.testj.domain.Goods} * DTO for {@link com.example.testj.domain.Goods}
*/ */
public class GoodsDto implements Serializable { public class GoodsDto implements Serializable {
private final Long id; private Long id;
private final String name; private String name;
private final Number price; private BigDecimal price;
public GoodsDto(Long id, String name, Number price) { public GoodsDto() {
this.id = id; }
this.name = name;
this.price = price; public GoodsDto(Goods goods) {
this.id = goods.getId();
this.name = goods.getName();
this.price = goods.getPrice();
} }
public Long getId() { public Long getId() {
return id; return id;
} }
public void setId(Long id) {
this.id = id;
}
public String getName() { public String getName() {
return name; return name;
} }
public Number getPrice() { public void setName(String name) {
this.name = name;
}
public BigDecimal getPrice() {
return price; return price;
} }
@Override public void setPrice(BigDecimal price) {
public String toString() { this.price = price;
return getClass().getSimpleName() + "(" +
"id = " + id + ", " +
"name = " + name + ", " +
"price = " + price + ")";
} }
} }
\ No newline at end of file
package com.example.testj.dto; package com.example.testj.service.dto;
import java.io.Serializable;
import java.util.List;
import com.example.testj.domain.Order; import com.example.testj.domain.Order;
import com.example.testj.domain.OrderLine;
import java.io.Serializable; public class OrderDto implements Serializable {
private Long id;
private String client;
private String date;
private String address;
public class OrderDTO implements Serializable { private List<OrderLine> orderLines;
public OrderDTO(Order order) {
public OrderDto() {
}
public OrderDto(Order order) {
this.id = order.getId(); this.id = order.getId();
this.client = order.getClient(); this.client = order.getClient();
this.address = order.getAddress(); this.address = order.getAddress();
this.date = order.getDate(); this.date = order.getDate();
this.orderLines = order.getOrderLines();
} }
private Long id;
private String client;
private String date;
private String address;
public String getDate() { public String getDate() {
return date; return date;
} }
...@@ -45,7 +53,11 @@ public class OrderDTO implements Serializable { ...@@ -45,7 +53,11 @@ public class OrderDTO implements Serializable {
return id; return id;
} }
public void setId(Long id) { public List<OrderLine> getOrderLines() {
this.id = id; return orderLines;
}
public void setOrderLines(List<OrderLine> orderLines) {
this.orderLines = orderLines;
} }
} }
package com.example.testj.service.dto;
import java.io.Serializable;
import com.example.testj.domain.Goods;
import com.example.testj.domain.Order;
import com.example.testj.domain.OrderLine;
public class OrderLineDto implements Serializable {
private Long id;
private Goods goods;
private Order order;
private int count;
public OrderLineDto() {
}
public OrderLineDto(OrderLine orderLine) {
this.id = orderLine.getId();
this.count = orderLine.getCount();
this.goods = orderLine.getGoods();
this.order = orderLine.getOrder();
}
public Goods getGoods() {
return this.goods;
}
public void setGoods(Goods goods) {
this.goods = goods;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public Long getId() {
return id;
}
public Order getOrder() {
return order;
}
}
\ No newline at end of file
package com.example.testj.utils;
import org.springframework.stereotype.Service;
import com.example.testj.domain.Goods;
import com.example.testj.domain.Order;
import com.example.testj.domain.OrderLine;
import com.example.testj.service.dto.GoodsDto;
import com.example.testj.service.dto.OrderDto;
import com.example.testj.service.dto.OrderLineDto;
@Service
public class MappingUtils {
public OrderDto toOrderDto(Order order) {
return new OrderDto(order);
}
public Order toOrder(OrderDto orderDto) {
Order order = new Order();
order.setId(orderDto.getId());
order.setClient(orderDto.getClient());
order.setDate(orderDto.getDate());
order.setAddress(orderDto.getAddress());
order.setOrderLines(orderDto.getOrderLines());
return order;
}
public OrderLineDto toOrderLineDto(OrderLine orderLine) {
return new OrderLineDto(orderLine);
}
public OrderLine toOrderLine(OrderLineDto orderLineDto) {
OrderLine orderLine = new OrderLine();
orderLine.setCount(orderLineDto.getCount());
return orderLine;
}
public GoodsDto toGoodsDto(Goods goods) {
return new GoodsDto(goods);
}
public Goods toGoods(GoodsDto goodsDto) {
Goods goods = new Goods();
goods.setId(goodsDto.getId());
goods.setName(goodsDto.getName());
goods.setPrice(goodsDto.getPrice());
return goods;
}
}
package com.example.testj.web.rest; package com.example.testj.web.rest;
import com.example.testj.domain.Goods; import org.springframework.data.web.PagedModel;
import com.example.testj.repository.GoodsRepository; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping; 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.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.util.List; import com.example.testj.exception.ResourceNotFoundException;
import com.example.testj.service.GoodsService;
import com.example.testj.service.dto.GoodsDto;
import com.example.testj.utils.MappingUtils;
@RestController @RestController
@RequestMapping("/api/goods")
@CrossOrigin(origins = "http://localhost:4200") @CrossOrigin(origins = "http://localhost:4200")
public class GoodsController { public class GoodsController {
private final GoodsRepository goodsRepository; private final GoodsService goodsService;
private final MappingUtils mappingUtils;
public GoodsController(GoodsRepository goodsRepository) { public GoodsController(GoodsService goodsService, MappingUtils mappingUtils) {
this.goodsRepository = goodsRepository; this.goodsService = goodsService;
this.mappingUtils = mappingUtils;
} }
@GetMapping("/goods") @GetMapping
public List<Goods> goods() { public PagedModel<GoodsDto> goods(
return (List<Goods>) goodsRepository.findAll(); @RequestHeader(name = "X-Page-Current", defaultValue = "0") Integer page,
@RequestHeader(name = "X-Page-Size", defaultValue = "10") Integer size,
@RequestHeader(name = "X-Sort-By", defaultValue = "id") String sortBy,
@RequestHeader(name = "X-Sort-Direction", defaultValue = "ASC") String sortDir) {
return goodsService.getAllGoods(page, size, sortBy, sortDir);
}
@GetMapping("/{id}")
public GoodsDto goods(@PathVariable Long id) {
return goodsService.getGoods(id);
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public GoodsDto createGoods(@RequestBody GoodsDto goods) {
return this.goodsService.createOrUpdate(mappingUtils.toGoods(goods));
}
@PutMapping("/{id}")
public GoodsDto updateGoods(@PathVariable Long id, @RequestBody GoodsDto goods) {
GoodsDto updatedOrder = goodsService.getGoods(id);
updatedOrder.setName(goods.getName());
updatedOrder.setPrice(goods.getPrice());
return updatedOrder;
}
@DeleteMapping("/{id}")
public ResponseEntity<HttpStatus> deleteGoods(@PathVariable Long id) {
try {
goodsService.deleteGoods(id);
return new ResponseEntity<>(HttpStatus.OK);
} catch (ResourceNotFoundException e) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
} }
} }
package com.example.testj.web.rest; package com.example.testj.web.rest;
import org.springframework.data.web.PagedModel;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
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.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import com.example.testj.domain.Order; import com.example.testj.domain.Order;
import com.example.testj.domain.RestErrorInfo;
import com.example.testj.dto.OrderDTO;
import com.example.testj.exception.ResourceNotFoundException; import com.example.testj.exception.ResourceNotFoundException;
import com.example.testj.repository.OrderRepository;
import com.example.testj.service.OrderService; import com.example.testj.service.OrderService;
import com.example.testj.service.dto.OrderDto;
import com.example.testj.utils.MappingUtils;
import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import jakarta.validation.Valid;
import org.springframework.data.web.PagedModel;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Optional; import jakarta.validation.Valid;
@RestController @RestController
@RequestMapping("/api") @RequestMapping("/api/orders")
@CrossOrigin(origins = "http://localhost:4200") @CrossOrigin(origins = "http://localhost:4200")
public class OrderController { public class OrderController {
private final OrderRepository orderRepository;
private final OrderService orderService; private final OrderService orderService;
private final MappingUtils mappingUtils;
public OrderController(OrderRepository orderRepository, OrderService orderService) { public OrderController(OrderService orderService, MappingUtils mappingUtils) {
this.orderRepository = orderRepository;
this.orderService = orderService; this.orderService = orderService;
this.mappingUtils = mappingUtils;
} }
@GetMapping("/orders") @GetMapping
public PagedModel<Order> getOrders(@RequestHeader(name = "X-Current-Page", defaultValue = "0") Integer page, public PagedModel<OrderDto> getOrders(
@RequestHeader(name = "X-Page-Size", defaultValue = "10") Integer size, @RequestHeader(name = "X-Page-Current", defaultValue = "0") Integer page,
@RequestHeader(name = "X-Sort-By", defaultValue = "id") String sortBy, @RequestHeader(name = "X-Page-Size", defaultValue = "10") Integer size,
@RequestHeader(name = "X-Sort-Direction", defaultValue = "ASC") String sortDir) { @RequestHeader(name = "X-Sort-By", defaultValue = "id") String sortBy,
@RequestHeader(name = "X-Sort-Direction", defaultValue = "ASC") String sortDir) {
return orderService.getOrders(page, size, sortBy, sortDir); return orderService.getOrders(page, size, sortBy, sortDir);
} }
@GetMapping("/orders/{id}") @GetMapping("/{id}")
public Order getOrder(@PathVariable Long id) { public OrderDto getOrder(@PathVariable Long id) {
return this.orderService.getOrder(id); return orderService.getOrder(id);
} }
@PostMapping("/orders") @PostMapping
public Order createOrder(@Valid @RequestBody Order order) { @ResponseStatus(HttpStatus.CREATED)
return orderService.saveOrUpdateOrder(order); public OrderDto createOrder(@Valid @RequestBody Order order) {
return orderService.createOrUpdateOrder(order);
} }
@PutMapping("/orders/{id}") @PutMapping("/{id}")
public Order updateOrder(@PathVariable Long id, @RequestBody Order order) { @ResponseStatus(HttpStatus.OK)
Order updatedOrder = orderService.getOrder(id); public OrderDto updateOrder(@PathVariable Long id, @RequestBody OrderDto order) {
updatedOrder.setClient(order.getClient());
updatedOrder.setAddress(order.getAddress()); OrderDto updatedOrder = orderService.getOrder(id);
updatedOrder.setDate(order.getDate()); updatedOrder.setClient(order.getClient());
return orderService.saveOrUpdateOrder(updatedOrder); updatedOrder.setAddress(order.getAddress());
updatedOrder.setDate(order.getDate());
return orderService.createOrUpdateOrder(mappingUtils.toOrder(updatedOrder));
} }
@PatchMapping("/orders/{id}") @PatchMapping("/{id}")
public Order patchOrder(@PathVariable Long id, @RequestBody JsonNode patch) throws JsonMappingException { public OrderDto patchOrder(@PathVariable Long id, @RequestBody JsonNode patch) throws JsonMappingException {
return orderService.patchOrder(id, patch); return orderService.patchOrder(id, patch);
} }
@DeleteMapping("/orders/{id}") @DeleteMapping("/{id}")
public ResponseEntity<HttpStatus> deleteOrder(@PathVariable Long id) throws ResourceNotFoundException { public ResponseEntity<HttpStatus> deleteOrder(@PathVariable Long id) throws ResourceNotFoundException {
try { try {
orderService.deleteOrder(id); orderService.deleteOrder(id);
...@@ -68,6 +83,5 @@ public class OrderController { ...@@ -68,6 +83,5 @@ public class OrderController {
} catch (ResourceNotFoundException e) { } catch (ResourceNotFoundException e) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND); return new ResponseEntity<>(HttpStatus.NOT_FOUND);
} }
} }
} }
package com.example.testj.web.rest;
import org.springframework.web.bind.annotation.CrossOrigin;
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.RestController;
import com.example.testj.service.OrderLineService;
import com.example.testj.service.dto.OrderLineDto;
import com.example.testj.utils.MappingUtils;
import jakarta.validation.Valid;
@RestController
@RequestMapping("/api/order-line")
@CrossOrigin(origins = "http://localhost:4200")
public class OrderLineController {
private final OrderLineService orderLineService;
private final MappingUtils mappingUtils;
public OrderLineController(OrderLineService orderLineService, MappingUtils mappingUtils) {
this.orderLineService = orderLineService;
this.mappingUtils = mappingUtils;
}
@GetMapping("/{id}")
public OrderLineDto get(@PathVariable Long id) {
return orderLineService.getOrderLine(id);
}
@PostMapping
public OrderLineDto createOrder(@Valid @RequestBody OrderLineDto orderLine) {
return orderLineService.createOrUpdateOrderLine(mappingUtils.toOrderLine(orderLine));
}
@PutMapping("/{id}")
public OrderLineDto updateOrder(@PathVariable Long id, @Valid @RequestBody OrderLineDto orderLine) {
OrderLineDto updateOrderLine = orderLineService.getOrderLine(id);
updateOrderLine.setCount(orderLine.getCount());
return orderLineService.createOrUpdateOrderLine(mappingUtils.toOrderLine(updateOrderLine));
}
}
...@@ -2,7 +2,7 @@ spring.application.name=testj ...@@ -2,7 +2,7 @@ spring.application.name=testj
spring.datasource.url=jdbc:h2:mem:testdb spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver spring.datasource.driverClassName=org.h2.Driver
spring.jpa.hibernate.ddl-auto=update spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.defer-datasource-initialization=true spring.jpa.defer-datasource-initialization=true
spring.h2.console.enabled=true spring.h2.console.enabled=true
......
This diff is collapsed.
{
"overrides": [
{
"files": "*.html",
"options": {
"parser": "angular"
}
}
]
}
# Testj # Ng17
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 18.0.2. This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.3.8.
## Development server ## Development server
...@@ -24,4 +24,4 @@ Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To u ...@@ -24,4 +24,4 @@ Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To u
## Further help ## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page. To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
...@@ -3,18 +3,12 @@ ...@@ -3,18 +3,12 @@
"version": 1, "version": 1,
"newProjectRoot": "projects", "newProjectRoot": "projects",
"projects": { "projects": {
"testj": { "ng17": {
"projectType": "application", "projectType": "application",
"schematics": { "schematics": {
"@schematics/angular:component": { "@schematics/angular:component": {
"style": "scss", "style": "scss",
"standalone": false "standalone": false
},
"@schematics/angular:directive": {
"standalone": false
},
"@schematics/angular:pipe": {
"standalone": false
} }
}, },
"root": "", "root": "",
...@@ -24,21 +18,15 @@ ...@@ -24,21 +18,15 @@
"build": { "build": {
"builder": "@angular-devkit/build-angular:application", "builder": "@angular-devkit/build-angular:application",
"options": { "options": {
"outputPath": "dist/testj", "outputPath": "dist/ng17",
"index": "src/index.html", "index": "src/index.html",
"browser": "src/main.ts", "browser": "src/main.ts",
"polyfills": [ "polyfills": ["zone.js"],
"zone.js"
],
"tsConfig": "tsconfig.app.json", "tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss", "inlineStyleLanguage": "scss",
"assets": [ "assets": ["src/favicon.ico", "src/assets"],
{
"glob": "**/*",
"input": "public"
}
],
"styles": [ "styles": [
"node_modules/bootstrap-icons/font/bootstrap-icons.scss",
"src/styles.scss" "src/styles.scss"
], ],
"scripts": [] "scripts": []
...@@ -48,13 +36,13 @@ ...@@ -48,13 +36,13 @@
"budgets": [ "budgets": [
{ {
"type": "initial", "type": "initial",
"maximumWarning": "500kB", "maximumWarning": "500kb",
"maximumError": "1MB" "maximumError": "1mb"
}, },
{ {
"type": "anyComponentStyle", "type": "anyComponentStyle",
"maximumWarning": "2kB", "maximumWarning": "2kb",
"maximumError": "4kB" "maximumError": "4kb"
} }
], ],
"outputHashing": "all" "outputHashing": "all"
...@@ -77,35 +65,28 @@ ...@@ -77,35 +65,28 @@
"builder": "@angular-devkit/build-angular:dev-server", "builder": "@angular-devkit/build-angular:dev-server",
"configurations": { "configurations": {
"production": { "production": {
"buildTarget": "testj:build:production" "buildTarget": "ng17:build:production"
}, },
"development": { "development": {
"buildTarget": "testj:build:development" "buildTarget": "ng17:build:development"
} }
}, },
"defaultConfiguration": "development" "defaultConfiguration": "development"
}, },
"extract-i18n": { "extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n" "builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"buildTarget": "ng17:build"
}
}, },
"test": { "test": {
"builder": "@angular-devkit/build-angular:karma", "builder": "@angular-devkit/build-angular:karma",
"options": { "options": {
"polyfills": [ "polyfills": ["zone.js", "zone.js/testing"],
"zone.js",
"zone.js/testing"
],
"tsConfig": "tsconfig.spec.json", "tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "scss", "inlineStyleLanguage": "scss",
"assets": [ "assets": ["src/favicon.ico", "src/assets"],
{ "styles": ["src/styles.scss"],
"glob": "**/*",
"input": "public"
}
],
"styles": [
"src/styles.scss"
],
"scripts": [] "scripts": []
} }
} }
......
This diff is collapsed.
{ {
"name": "testj", "name": "ng17",
"version": "0.0.0", "version": "0.0.0",
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
...@@ -10,22 +10,27 @@ ...@@ -10,22 +10,27 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "^18.0.0", "@angular/animations": "^17.3.0",
"@angular/common": "^18.0.0", "@angular/common": "^17.3.0",
"@angular/compiler": "^18.0.0", "@angular/compiler": "^17.3.0",
"@angular/core": "^18.0.0", "@angular/core": "^17.3.0",
"@angular/forms": "^18.0.0", "@angular/forms": "^17.3.0",
"@angular/platform-browser": "^18.0.0", "@angular/platform-browser": "^17.3.0",
"@angular/platform-browser-dynamic": "^18.0.0", "@angular/platform-browser-dynamic": "^17.3.0",
"@angular/router": "^18.0.0", "@angular/router": "^17.3.0",
"@ng-bootstrap/ng-bootstrap": "^16.0.0",
"@popperjs/core": "^2.11.8",
"bootstrap": "^5.3.2",
"bootstrap-icons": "^1.11.3",
"rxjs": "~7.8.0", "rxjs": "~7.8.0",
"tslib": "^2.3.0", "tslib": "^2.3.0",
"zone.js": "~0.14.3" "zone.js": "~0.14.3"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "^18.0.2", "@angular-devkit/build-angular": "^17.3.8",
"@angular/cli": "^18.0.2", "@angular/cli": "^17.3.8",
"@angular/compiler-cli": "^18.0.0", "@angular/compiler-cli": "^17.3.0",
"@angular/localize": "^17.3.0",
"@types/jasmine": "~5.1.0", "@types/jasmine": "~5.1.0",
"jasmine-core": "~5.1.0", "jasmine-core": "~5.1.0",
"karma": "~6.4.0", "karma": "~6.4.0",
...@@ -33,6 +38,7 @@ ...@@ -33,6 +38,7 @@
"karma-coverage": "~2.2.0", "karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0", "karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0", "karma-jasmine-html-reporter": "~2.1.0",
"prettier": "^3.3.2",
"typescript": "~5.4.2" "typescript": "~5.4.2"
} }
} }
.main {
}
.content {
width: 100%;
padding: 20px;
}
...@@ -3,17 +3,25 @@ import { BrowserModule } from '@angular/platform-browser'; ...@@ -3,17 +3,25 @@ import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import {provideHttpClient} from "@angular/common/http"; import { provideHttpClient } from '@angular/common/http';
import {
NgbDateAdapter,
NgbDateParserFormatter,
NgbModule,
} from '@ng-bootstrap/ng-bootstrap';
import {
CustomDateAdapter,
CustomDateParserFormatter,
} from './core/adapter/custom-date.adapter';
@NgModule({ @NgModule({
declarations: [ declarations: [AppComponent],
AppComponent imports: [BrowserModule, AppRoutingModule, NgbModule],
providers: [
provideHttpClient(),
{ provide: NgbDateAdapter, useClass: CustomDateAdapter },
{ provide: NgbDateParserFormatter, useClass: CustomDateParserFormatter },
], ],
imports: [ bootstrap: [AppComponent],
BrowserModule,
AppRoutingModule
],
providers: [provideHttpClient()],
bootstrap: [AppComponent]
}) })
export class AppModule { } export class AppModule {}
import { Injectable } from '@angular/core';
import {
NgbDateParserFormatter,
NgbDateStruct,
} from '@ng-bootstrap/ng-bootstrap';
/**
* This Service handles how the date is represented in scripts i.e. ngModel.
*/
@Injectable()
export class CustomDateAdapter {
readonly DELIMITER = '/';
fromModel(value: string | null): NgbDateStruct | null {
if (value) {
const date = value.split(this.DELIMITER);
return {
day: parseInt(date[0], 10),
month: parseInt(date[1], 10),
year: parseInt(date[2], 10),
};
}
return null;
}
toModel(date: NgbDateStruct | null): string | null {
return date
? date.day + this.DELIMITER + date.month + this.DELIMITER + date.year
: null;
}
}
/**
* This Service handles how the date is rendered and parsed from keyboard i.e. in the bound input field.
*/
@Injectable()
export class CustomDateParserFormatter extends NgbDateParserFormatter {
readonly DELIMITER = '/';
parse(value: string): NgbDateStruct | null {
if (value) {
const date = value.split(this.DELIMITER);
return {
day: parseInt(date[0], 10),
month: parseInt(date[1], 10),
year: parseInt(date[2], 10),
};
}
return null;
}
format(date: NgbDateStruct | null): string {
return date
? date.day + this.DELIMITER + date.month + this.DELIMITER + date.year
: '';
}
}
export interface User { export interface Goods {
id: number; id: number;
name: string; name: string;
price: number; price: number;
......
export interface OrderLine {
id: number;
order_id: number;
goods_id: number;
count: number;
}
import { Goods } from './goods.model';
export interface Order { export interface Order {
id: number; id: number;
address: string; address: string;
client: string; client: string;
date: string; date: string;
orderLines: OrderLine[];
}
export interface OrderLine {
id: number;
goods: Goods;
count: number;
}
export interface OrderLineReq {
id?: number;
order_id: number;
goods_id: number;
count: number;
} }
export interface PagedResponseModel<T> {
content: T[];
page: PageModel;
}
export interface PageModel {
number: number;
size: number;
totalElements: number;
totalPages: number;
}
export interface ControlsValidations {
[key: string]: ValidationMessage[];
}
export interface ValidationMessage {
type: string;
message: string;
params?: object;
}
import { TestBed } from '@angular/core/testing';
import { GoodsService } from './goods.service';
describe('GoodsService', () => {
let service: GoodsService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(GoodsService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
import { Injectable } from '@angular/core';
import { environment } from '../../../environments/environment';
import {
HttpClient,
HttpErrorResponse,
HttpHeaders,
} from '@angular/common/http';
import { catchError, EMPTY, Observable } from 'rxjs';
import { PagedResponseModel } from '../model/paged-response.model';
import { Goods } from '../model/goods.model';
@Injectable({
providedIn: 'root',
})
export class GoodsService {
private readonly apiUrl = `${environment.API_ROOT}${environment.API_PREFIX}${environment.GOODS_URL}`;
private page = 0;
private limit = 10;
constructor(private http: HttpClient) {}
get(
page = this.page,
limit = this.limit
): Observable<PagedResponseModel<Goods>> {
const url = `${this.apiUrl}`;
const headers = new HttpHeaders({
'Content-Type': 'application/json',
'X-Page-Current': String(page),
'X-Page-Size': String(limit),
});
return this.http.get<PagedResponseModel<Goods>>(url, { headers });
}
put(goods: Goods): Observable<Goods> {
const url = `${this.apiUrl}/${goods.id}`;
return this.http.put<Goods>(url, goods);
}
post(goods: Goods): Observable<Goods> {
const url = this.apiUrl;
return this.http.post<Goods>(url, goods);
}
delete(id: number) {
const url = `${this.apiUrl}/${id}`;
return this.http.delete(url).pipe(
catchError((err: HttpErrorResponse) => {
console.dir(err);
return EMPTY;
})
);
}
}
import { TestBed } from '@angular/core/testing';
import { OrderLineService } from './order-line.service';
describe('OrderLineService', () => {
let service: OrderLineService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(OrderLineService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { catchError, EMPTY } from 'rxjs';
import { environment } from '../../../environments/environment';
import { OrderLine, OrderLineReq } from '../model/order.model';
@Injectable({
providedIn: 'root',
})
export class OrderLineService {
private readonly apiUrl = `${environment.API_ROOT}${environment.API_PREFIX}${environment.ORDER_LINE_URL}`;
constructor(private http: HttpClient) {}
put(ol: OrderLineReq) {
const url = `${this.apiUrl}/${ol.id}`;
return this.http.put<OrderLine>(url, ol);
}
post(ol: OrderLineReq) {
const url = this.apiUrl;
return this.http.post<OrderLine>(url, ol);
}
delete(id: number) {
const url = `${this.apiUrl}/${id}`;
return this.http.delete(url).pipe(
catchError((err: HttpErrorResponse) => {
console.dir(err);
return EMPTY;
})
);
}
}
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http'; import {
import { Observable } from 'rxjs'; HttpClient,
HttpErrorResponse,
HttpHeaders,
} from '@angular/common/http';
import { catchError, EMPTY, Observable } from 'rxjs';
import { environment } from '../../../environments/environment'; import { environment } from '../../../environments/environment';
import { Order } from '../model/order.model'; import { Order } from '../model/order.model';
import { PagedResponseModel } from '../model/paged-response.model';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class OrderService { export class OrderService {
private readonly apiUrl = `${environment.API_ROOT}${environment.API_PREFIX}${environment.ORDER_URL}`; private readonly apiUrl = `${environment.API_ROOT}${environment.API_PREFIX}${environment.ORDER_URL}`;
private page = 1; private page = 0;
private limit = 10; private size = 10;
constructor(private http: HttpClient) {} constructor(private http: HttpClient) {}
getOrders(): Observable<Order[]> { getOrders(
page = this.page,
size = this.size
): Observable<PagedResponseModel<Order>> {
const url = `${this.apiUrl}`; const url = `${this.apiUrl}`;
const headers = new HttpHeaders({ const headers = new HttpHeaders({
'Content-Type': 'application/json', 'Content-Type': 'application/json',
Page: String(this.page), 'X-Page-Current': String(page),
Limit: String(this.limit), 'X-Page-Size': String(size),
}); });
return this.http.get<Order[]>(url, { headers }); return this.http.get<PagedResponseModel<Order>>(url, { headers });
}
getOrder(id: number): Observable<Order> {
const url = `${this.apiUrl}/${id}`;
return this.http.get<Order>(url);
}
putOrder(order: Order) {
const url = `${this.apiUrl}/${order.id}`;
return this.http.put<Order>(url, order);
}
postOrder(order: Order) {
const url = this.apiUrl;
return this.http.post<Order>(url, order);
}
deleteOrder(id: number) {
const url = `${this.apiUrl}/${id}`;
return this.http.delete(url).pipe(
catchError((err: HttpErrorResponse) => {
console.dir(err);
return EMPTY;
})
);
} }
} }
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">Product</h4>
<button
type="button"
class="btn-close"
aria-label="Close"
(click)="modal?.close()"
></button>
</div>
<div class="modal-body">
<app-goods-form
[product]="product"
(isUpdate)="goodsUpdate()"
></app-goods-form>
</div>
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { GoodsEditModalComponent } from './goods-edit-modal.component';
describe('GoodsEditModalComponent', () => {
let component: GoodsEditModalComponent;
let fixture: ComponentFixture<GoodsEditModalComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [GoodsEditModalComponent]
})
.compileComponents();
fixture = TestBed.createComponent(GoodsEditModalComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import { takeUntil } from 'rxjs';
import { environment } from '../../../../../environments/environment';
import { AbstractBasicComponent } from '../../../../core/abstract/abstract-component';
import { Goods } from '../../../../core/model/goods.model';
const DEV = !environment.production;
@Component({
selector: 'app-goods-edit-modal',
templateUrl: './goods-edit-modal.component.html',
styleUrl: './goods-edit-modal.component.scss',
})
export class GoodsEditModalComponent extends AbstractBasicComponent {
@Input() product!: Goods;
@Output() isUpdate = new EventEmitter<boolean>();
modal: NgbModalRef | undefined;
constructor(modalService: NgbModal) {
super();
modalService.activeInstances
.pipe(takeUntil(this.destroy$))
.subscribe((mls) => {
this.modal = mls[mls.length - 1];
});
}
goodsUpdate() {
this.modal?.close();
this.isUpdate.emit(true);
}
}
<table class="table table-striped">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Price</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
@for (product of goods; track product.id){
<tr>
<th scope="row">{{ product.id }}</th>
<td>{{ product.name }}</td>
<td>{{ product.price | currency }}</td>
<td>
<button
type="button"
class="btn btn-sm btn-outline-secondary me-2"
(click)="openEdit(product)"
>
Edit
</button>
<button
type="button"
class="btn btn-sm btn-outline-danger me-2"
(click)="deleteProduct(product.id)"
>
Delete
</button>
</td>
</tr>
}
</tbody>
</table>
@if (page) {
<ngb-pagination
[page]="page.number + 1"
(pageChange)="pageNumberChange($event - 1)"
[pageSize]="page.size"
[collectionSize]="page.totalElements"
></ngb-pagination>
}
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { GoodsListComponent } from './goods-list.component';
describe('GoodsListComponent', () => {
let component: GoodsListComponent;
let fixture: ComponentFixture<GoodsListComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [GoodsListComponent]
})
.compileComponents();
fixture = TestBed.createComponent(GoodsListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Goods } from '../../../../core/model/goods.model';
import { PageModel } from '../../../../core/model/paged-response.model';
@Component({
selector: 'app-goods-list',
templateUrl: './goods-list.component.html',
styleUrl: './goods-list.component.scss',
})
export class GoodsListComponent {
@Input() goods!: Goods[];
@Input() page!: PageModel;
@Output() pageNumber = new EventEmitter<number>();
@Output() openModal = new EventEmitter<Goods>();
@Output() delete = new EventEmitter<number>();
pageNumberChange(page: number) {
this.pageNumber.emit(page);
}
openEdit(product: Goods) {
this.openModal.emit(product);
}
deleteProduct(id: number) {
this.delete.emit(id);
}
}
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">Order</h4>
<button
type="button"
class="btn-close"
aria-label="Close"
(click)="modal.close()"
></button>
</div>
<div class="modal-body">
<app-order-form
[order]="order"
(orderChange)="orderChanged($event)"
></app-order-form>
</div>
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { OrderEditModalComponent } from './order-edit-modal.component';
describe('OrderEditModalComponent', () => {
let component: OrderEditModalComponent;
let fixture: ComponentFixture<OrderEditModalComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [OrderEditModalComponent]
})
.compileComponents();
fixture = TestBed.createComponent(OrderEditModalComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import { takeUntil } from 'rxjs';
import { AbstractBasicComponent } from '../../../../core/abstract/abstract-component';
import { Order } from '../../../../core/model/order.model';
@Component({
selector: 'app-order-edit-modal',
templateUrl: './order-edit-modal.component.html',
styleUrl: './order-edit-modal.component.scss',
})
export class OrderEditModalComponent extends AbstractBasicComponent {
@Input() order!: Order;
@Output() orderChange = new EventEmitter<Order>();
modal!: NgbModalRef;
constructor(modalService: NgbModal) {
super();
modalService.activeInstances
.pipe(takeUntil(this.destroy$))
.subscribe((mls) => {
this.modal = mls[mls.length - 1];
});
}
orderChanged(order: Order) {
this.orderChange.emit(order);
this.modal.close();
}
}
<ul> <table class="table table-striped">
<li *ngFor="let order of orders"> <thead>
<dl> <tr>
<dt>Client</dt> <th scope="col">#</th>
<dd> <th scope="col">Client</th>
{{ order.client }} <th scope="col">Address</th>
</dd> <th scope="col">Date</th>
<dt>Date</dt> <th scope="col">Products</th>
<dd>{{ order.date | date }}</dd> <th scope="col"></th>
</dl> </tr>
</li> </thead>
</ul> <tbody>
@for (order of orders; track order.id){
<tr>
<th scope="row">{{ order.id }}</th>
<td>{{ order.client }}</td>
<td>{{ order.address }}</td>
<td>{{ order.date | date }}</td>
<td>{{ order.orderLines.length }}</td>
<td>
<a
class="btn btn-sm btn-outline-secondary me-2"
role="button"
[routerLink]="['order', order.id]"
>
Details
</a>
<button
type="button"
class="btn btn-sm btn-outline-secondary me-2"
(click)="openOrder(order)"
>
Edit
</button>
<button
type="button"
class="btn btn-sm btn-outline-danger me-2"
(click)="deleteOrder(order.id)"
>
Delete
</button>
</td>
</tr>
}
</tbody>
</table>
@if (page) {
<ngb-pagination
[page]="page.number + 1"
(pageChange)="pageNumberChange($event - 1)"
[pageSize]="page.size"
[collectionSize]="page.totalElements"
></ngb-pagination>
}
import {ChangeDetectionStrategy, Component, Input} from '@angular/core'; import { Component, EventEmitter, Input, Output } from '@angular/core';
import {AsyncPipe, DatePipe, NgForOf} from "@angular/common"; import { Order } from '../../../../core/model/order.model';
import {Observable} from "rxjs"; import { PageModel } from '../../../../core/model/paged-response.model';
import {Order} from "../../../../core/model/order.model";
import {AbstractBasicComponent} from "../../../../core/abstract/abstract-component";
@Component({ @Component({
selector: 'app-orders-list', selector: 'app-orders-list',
...@@ -11,8 +9,21 @@ import {AbstractBasicComponent} from "../../../../core/abstract/abstract-compone ...@@ -11,8 +9,21 @@ import {AbstractBasicComponent} from "../../../../core/abstract/abstract-compone
}) })
export class OrdersListComponent { export class OrdersListComponent {
@Input() orders!: Order[]; @Input() orders!: Order[];
@Input() page!: PageModel;
@Output() pageNumber = new EventEmitter<number>();
constructor( @Output() openModal = new EventEmitter<Order>();
){ @Output() delete = new EventEmitter<number>();
pageNumberChange(page: number) {
this.pageNumber.emit(page);
}
openOrder(order: Order) {
this.openModal.emit(order);
}
deleteOrder(id: number) {
this.delete.emit(id);
} }
} }
<app-orders-list [orders]="(orders$ | async)!"></app-orders-list> <ul ngbNav #nav="ngbNav" class="nav-tabs" [destroyOnHide]="false">
<li ngbNavItem>
<button ngbNavLink>Orders</button>
<ng-template ngbNavContent>
<div class="py-2 float-end">
<a
role="button"
[routerLink]="['order']"
class="btn btn-outline-secondary me-2"
>
New order
</a>
</div>
<ng-container *ngIf="ordersSubj$ | async as orders; else loading">
<app-orders-list
[orders]="orders.content"
[page]="orders.page"
(pageNumber)="ordersPageNumber($event)"
(openModal)="openOrderModal($event)"
(delete)="deleteOrder($event)"
></app-orders-list>
</ng-container>
<ng-template #loading> loading... </ng-template>
</ng-template>
</li>
<li ngbNavItem>
<button ngbNavLink>Products</button>
<ng-template ngbNavContent>
<div class="py-2 float-end">
<button
class="btn btn-outline-secondary me-2"
(click)="openNewProductModal()"
>
New product
</button>
</div>
<ng-container *ngIf="goodsSubj$ | async as goods; else loading">
<app-goods-list
[goods]="goods.content"
[page]="goods.page"
(pageNumber)="goodsPageNumber($event)"
(openModal)="openGoodsModal($event)"
(delete)="deleteGoods($event)"
></app-goods-list>
</ng-container>
<ng-template #loading> loading... </ng-template>
</ng-template>
</li>
</ul>
<div [ngbNavOutlet]="nav" class="mt-2"></div>
import {Component, OnInit} from '@angular/core'; import { Component, OnInit } from '@angular/core';
import {Observable} from "rxjs"; import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import {Order} from "../../core/model/order.model"; import { Subject, concatMap, filter, take } from 'rxjs';
import {OrderService} from "../../core/service/order.service"; import { AbstractBasicComponent } from '../../core/abstract/abstract-component';
import {AbstractBasicComponent} from "../../core/abstract/abstract-component"; import { Goods } from '../../core/model/goods.model';
import { Order } from '../../core/model/order.model';
import {
PageModel,
PagedResponseModel,
} from '../../core/model/paged-response.model';
import { GoodsService } from '../../core/service/goods.service';
import { OrderService } from '../../core/service/order.service';
import { GoodsEditModalComponent } from './components/goods-edit-modal/goods-edit-modal.component';
import { OrderEditModalComponent } from './components/order-edit-modal/order-edit-modal.component';
@Component({ @Component({
selector: 'app-index-page', selector: 'app-index-page',
templateUrl: './index-page.component.html', templateUrl: './index-page.component.html',
styleUrl: './index-page.component.scss' styleUrl: './index-page.component.scss',
}) })
export class IndexPageComponent extends AbstractBasicComponent implements OnInit { export class IndexPageComponent
extends AbstractBasicComponent
implements OnInit
{
protected ordersSubj$ = new Subject<PagedResponseModel<Order>>();
protected goodsSubj$ = new Subject<PagedResponseModel<Goods>>();
protected orders$!: Observable<Order[]>; goodsPage!: PageModel;
orderPage!: PageModel;
constructor(private orderService: OrderService) { constructor(
private orderService: OrderService,
private goodsService: GoodsService,
private modalService: NgbModal,
) {
super(); super();
} }
ngOnInit() { ngOnInit() {
this.orders$ = this.orderService.getOrders(); this.orderService.getOrders().subscribe((resp) => {
this.orderPage = resp.page;
this.ordersSubj$.next(resp);
});
this.goodsService.get().subscribe((resp) => {
this.goodsPage = resp.page;
this.goodsSubj$.next(resp);
});
}
ordersPageNumber(page: number) {
this.orderService.getOrders(page).subscribe((resp) => {
this.orderPage = resp.page;
this.ordersSubj$.next(resp);
});
}
goodsPageNumber(page: number) {
this.goodsService.get(page).subscribe((resp) => {
this.goodsPage = resp.page;
this.goodsSubj$.next(resp);
});
}
deleteOrder(id: number) {
this.orderService
.deleteOrder(id)
.pipe(concatMap(() => this.orderService.getOrders(this.orderPage.number)))
.subscribe((resp) => {
this.orderPage = resp.page;
this.ordersSubj$.next(resp);
});
}
deleteGoods(id: number) {
this.goodsService
.delete(id)
.pipe(concatMap(() => this.goodsService.get(this.goodsPage.number)))
.subscribe((resp) => {
this.goodsPage = resp.page;
this.goodsSubj$.next(resp);
});
}
openOrderModal(order: Order): void {
const orderModalRef = this.modalService.open(OrderEditModalComponent);
orderModalRef.componentInstance.order = order;
orderModalRef.componentInstance.orderChange
.pipe(
take(1),
concatMap(() => this.orderService.getOrders(this.orderPage.number)),
)
.subscribe((resp: PagedResponseModel<Order>) => {
this.orderPage = resp.page;
this.ordersSubj$.next(resp);
});
}
openGoodsModal(product: Goods): void {
const goodsModalRef = this.modalService.open(GoodsEditModalComponent);
goodsModalRef.componentInstance.product = product;
goodsModalRef.componentInstance.isUpdate
.pipe(
filter(Boolean),
concatMap(() => this.goodsService.get(this.goodsPage.number)),
)
.subscribe((resp: PagedResponseModel<Goods>) => {
this.goodsPage = resp.page;
this.goodsSubj$.next(resp);
});
}
openNewProductModal(): void {
const goodsModalRef = this.modalService.open(GoodsEditModalComponent);
goodsModalRef.componentInstance.isUpdate
.pipe(
filter(Boolean),
concatMap(() => this.goodsService.get(this.goodsPage.number)),
)
.subscribe((resp: PagedResponseModel<Goods>) => {
console.dir(resp);
this.goodsPage = resp.page;
this.goodsSubj$.next(resp);
});
} }
} }
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">Add product</h4>
<button
type="button"
class="btn-close"
aria-label="Close"
(click)="modal?.close()"
></button>
</div>
<div class="modal-body">
@if (goods) {
<table class="table table-striped">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Price</th>
<th scope="col">Count</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
@for (product of goods.content; track product.id) {
<tr>
<th scope="row">{{ product.id }}</th>
<td>{{ product.name }}</td>
<td>{{ product.price | currency }}</td>
<td>
<input
type="number"
class="w-50 form-control"
[value]="productsCount(product.id)"
#count
/>
</td>
<td>
<button
type="button"
[ngClass]="
productsCount(product.id)
? 'btn-outline-primary'
: 'btn-outline-secondary'
"
class="btn btn-sm me-2"
(click)="addOrUpdate(product.id, +count.value)"
>
{{ productsCount(product.id) ? "Update" : "Add" }}
</button>
</td>
</tr>
}
</tbody>
</table>
@if (goods) {
<ngb-pagination
[page]="goods.page.number + 1"
(pageChange)="pageNumberChange($event - 1)"
[pageSize]="goods.page.size"
[collectionSize]="goods.page.totalElements"
></ngb-pagination>
}
} @else {
loading...
}
<ng-template #loading> loading... </ng-template>
</div>
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AddProductModalComponent } from './add-product-modal.component';
describe('AddProductModalComponent', () => {
let component: AddProductModalComponent;
let fixture: ComponentFixture<AddProductModalComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [AddProductModalComponent]
})
.compileComponents();
fixture = TestBed.createComponent(AddProductModalComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import { Goods } from '../../../../core/model/goods.model';
import { AbstractBasicComponent } from '../../../../core/abstract/abstract-component';
import { takeUntil } from 'rxjs';
import { PagedResponseModel } from '../../../../core/model/paged-response.model';
import { Order } from '../../../../core/model/order.model';
import { UpdateCount } from '../../order-page.component';
@Component({
selector: 'app-add-product-modal',
templateUrl: './add-product-modal.component.html',
styleUrl: './add-product-modal.component.scss',
})
export class AddProductModalComponent extends AbstractBasicComponent {
@Input() goods!: PagedResponseModel<Goods>;
@Input() order!: Order;
@Output() pageNumber = new EventEmitter<number>();
@Output() orderLineChange = new EventEmitter<UpdateCount>();
modal: NgbModalRef | undefined;
constructor(modalService: NgbModal) {
super();
modalService.activeInstances
.pipe(takeUntil(this.destroy$))
.subscribe((mls) => {
this.modal = mls[mls.length - 1];
});
}
addOrUpdate(goods_id: number, count: number) {
this.orderLineChange.emit({
goods_id,
count,
});
}
productsCount(productId: number): number {
return (
this.order.orderLines.find((ol) => ol.goods.id === productId)?.count ?? 0
);
}
pageNumberChange(page: number) {
this.pageNumber.emit(page);
}
}
<h3>Products</h3>
<table class="table table-striped">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Price</th>
<th scope="col">Count</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
@for (orderLine of orderLines; let i = $index; track orderLine.id) {
<tr>
<td>{{ i + 1 }}</td>
<td>{{ orderLine.goods.name }}</td>
<td>{{ orderLine.goods.price | currency }}</td>
<td>{{ orderLine.count }}</td>
<td>
<button
type="button"
class="btn btn-sm btn-outline-danger me-2"
(click)="deleteLine(orderLine.id)"
>
Delete
</button>
</td>
</tr>
}
@if (orderLines.length === 0) {
<tr>
<td colspan="5" class="text-center">Nothing</td>
</tr>
}
</tbody>
</table>
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { OrderLineListComponent } from './order-line-list.component';
describe('OrderLineListComponent', () => {
let component: OrderLineListComponent;
let fixture: ComponentFixture<OrderLineListComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [OrderLineListComponent]
})
.compileComponents();
fixture = TestBed.createComponent(OrderLineListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { OrderLine } from '../../../../core/model/order.model';
@Component({
selector: 'app-order-line-list',
templateUrl: './order-line-list.component.html',
styleUrl: './order-line-list.component.scss',
})
export class OrderLineListComponent {
@Input() orderLines!: OrderLine[];
@Output() delete = new EventEmitter<number>();
deleteLine(id: number) {
this.delete.emit(id);
}
}
<ul class="nav">
<li class="nav-item">
<div class="py-2">
<a class="nav-link active" aria-current="page" [routerLink]="'/'"
><i class="bi bi-arrow-left me-2"></i>Back</a
>
</div>
</li>
</ul>
@if (isNew) {
<h3>New order</h3>
<app-order-form></app-order-form>
} @else {
<h3>Order</h3>
@if (orderSubj$ | async; as order) {
<table class="table">
<tr>
<th>Client</th>
<td>{{ order.client }}</td>
</tr>
<tr>
<th>Address</th>
<td>{{ order.address }}</td>
</tr>
<tr>
<th>Date</th>
<td>{{ order.date }}</td>
</tr>
</table>
<div class="py-2 float-end">
<button
class="btn btn-outline-secondary me-2"
(click)="openAddProductModal()"
>
Add products
</button>
</div>
<app-order-line-list
(delete)="deleteLine($event)"
(add)="openAddProductModal()"
[orderLines]="order.orderLines"
></app-order-line-list>
} @else {
<div>Loading...</div>
}
}
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { OrderPageComponent } from './order-page.component';
describe('OrderPageComponent', () => {
let component: OrderPageComponent;
let fixture: ComponentFixture<OrderPageComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [OrderPageComponent]
})
.compileComponents();
fixture = TestBed.createComponent(OrderPageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { Subject, concatMap, takeUntil } from 'rxjs';
import { environment } from '../../../environments/environment';
import { AbstractBasicComponent } from '../../core/abstract/abstract-component';
import { Goods } from '../../core/model/goods.model';
import { Order } from '../../core/model/order.model';
import { PagedResponseModel } from '../../core/model/paged-response.model';
import { GoodsService } from '../../core/service/goods.service';
import { OrderLineService } from '../../core/service/order-line.service';
import { OrderService } from '../../core/service/order.service';
import { AddProductModalComponent } from './components/add-product-modal/add-product-modal.component';
const DEV = !environment.production;
@Component({
selector: 'app-order-page',
templateUrl: './order-page.component.html',
styleUrl: './order-page.component.scss',
})
export class OrderPageComponent
extends AbstractBasicComponent
implements OnInit
{
form: FormGroup;
orderSubj$ = new Subject<Order>();
order!: Order;
private id!: number;
constructor(
private orderService: OrderService,
private orderLineService: OrderLineService,
private goodsService: GoodsService,
private modalService: NgbModal,
fb: FormBuilder,
route: ActivatedRoute,
) {
super();
this.form = fb.group({
client: ['', Validators.required],
date: ['', Validators.required],
address: ['', Validators.required],
});
route.params
.pipe(takeUntil(this.destroy$))
.subscribe((params) => (this.id = +params['id']));
}
public get isNew(): boolean {
return this.id === null || isNaN(this.id);
}
ngOnInit(): void {
if (!this.isNew) {
this.getData(this.id);
}
}
deleteLine(id: number) {
this.orderLineService.delete(id).subscribe({
next: (res) => {
this.getData(this.order.id);
},
error: (err) => {
if (DEV) {
console.dir(`Error: ${err}`);
}
},
});
}
getData(id: number) {
this.orderService.getOrder(id).subscribe((order) => {
this.order = order;
this.orderSubj$.next(order);
});
}
openAddProductModal(): void {
const orderModalRef = this.modalService.open(AddProductModalComponent, {
size: 'xl',
});
this.goodsService
.get()
.subscribe((resp) => (orderModalRef.componentInstance.goods = resp));
orderModalRef.componentInstance.order = this.order;
orderModalRef.componentInstance.pageNumber
.pipe(concatMap((page: number) => this.goodsService.get(page)))
.subscribe((resp: PagedResponseModel<Goods>) => {
orderModalRef.componentInstance.goods = resp;
});
orderModalRef.componentInstance.orderLineChange
.pipe(
concatMap((v: UpdateCount) => {
const orderLineId = this.order.orderLines.find(
(ol) => ol.goods.id === v.goods_id,
)?.id;
if (orderLineId) {
return this.orderLineService.put({
id: orderLineId,
order_id: this.order.id,
goods_id: v.goods_id,
count: v.count,
});
}
return this.orderLineService.post({
order_id: this.order.id,
goods_id: v.goods_id,
count: v.count,
});
}),
)
.subscribe((resp: any) => {
console.dir(resp);
});
}
previousState(): void {
window.history.back();
}
}
export interface UpdateCount {
goods_id: number;
count: number;
}
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router';
import { IndexPageComponent } from './index-page/index-page.component'; import { IndexPageComponent } from './index-page/index-page.component';
import { OrderPageComponent } from './order-page/order-page.component';
const routes: Routes = [ const routes: Routes = [
{path: '', component: IndexPageComponent} { path: '', component: IndexPageComponent },
{ path: 'order', component: OrderPageComponent },
{ path: 'order/:id', component: OrderPageComponent },
]; ];
@NgModule({ @NgModule({
......
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { PagesRoutingModule } from './pages-routing.module'; import { SharedModule } from '../shared/shared.module';
import { IndexPageComponent } from './index-page/index-page.component'; import { GoodsEditModalComponent } from './index-page/components/goods-edit-modal/goods-edit-modal.component';
import { GoodsListComponent } from './index-page/components/goods-list/goods-list.component';
import { OrderEditModalComponent } from './index-page/components/order-edit-modal/order-edit-modal.component';
import { OrdersListComponent } from './index-page/components/orders-list/orders-list.component'; import { OrdersListComponent } from './index-page/components/orders-list/orders-list.component';
import { IndexPageComponent } from './index-page/index-page.component';
import { AddProductModalComponent } from './order-page/components/add-product-modal/add-product-modal.component';
import { OrderLineListComponent } from './order-page/components/order-line-list/order-line-list.component';
import { OrderPageComponent } from './order-page/order-page.component';
import { PagesRoutingModule } from './pages-routing.module';
@NgModule({ @NgModule({
declarations: [IndexPageComponent, OrdersListComponent], declarations: [
imports: [CommonModule, PagesRoutingModule], IndexPageComponent,
OrderPageComponent,
OrdersListComponent,
GoodsListComponent,
OrderLineListComponent,
GoodsEditModalComponent,
AddProductModalComponent,
OrderEditModalComponent,
],
imports: [CommonModule, SharedModule, PagesRoutingModule],
}) })
export class PagesModule {} export class PagesModule {}
@for (validation of messages; track validation) {
@if (
control.hasError(validation.type) && (control.touched || control.dirty)
) {
{{ validation.message }}
}
}
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormErrorsComponent } from './form-errors.component';
describe('FormErrorsComponent', () => {
let component: FormErrorsComponent;
let fixture: ComponentFixture<FormErrorsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [FormErrorsComponent]
})
.compileComponents();
fixture = TestBed.createComponent(FormErrorsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, Input } from '@angular/core';
import { AbstractControl } from '@angular/forms';
import { ValidationMessage } from '../../core/model/validation.model';
@Component({
selector: 'app-form-errors',
templateUrl: './form-errors.component.html',
styleUrl: './form-errors.component.scss',
})
export class FormErrorsComponent {
@Input() messages!: ValidationMessage[];
@Input() control!: AbstractControl;
}
<form [formGroup]="form">
<div class="mb-3">
<div class="mb-3">
<label for="nameInput" class="form-label">Name</label>
<input
class="form-control"
id="nameInput"
placeholder="John Dow"
formControlName="name"
/>
</div>
<div class="mb-3">
<label for="priceInput" class="form-label">Price</label>
<input
type="number"
step="0.01"
class="form-control"
id="priceInput"
placeholder="123.00"
formControlName="price"
/>
</div>
</div>
</form>
<button type="button" class="btn btn-outline-secondary" (click)="save()">
{{ isNew ? "Create" : "Save" }}
</button>
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { GoodsFormComponent } from './goods-form.component';
describe('GoodsFormComponent', () => {
let component: GoodsFormComponent;
let fixture: ComponentFixture<GoodsFormComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [GoodsFormComponent]
})
.compileComponents();
fixture = TestBed.createComponent(GoodsFormComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { environment } from '../../../../environments/environment';
import { Goods } from '../../../core/model/goods.model';
import { GoodsService } from '../../../core/service/goods.service';
const DEV = !environment.production;
@Component({
selector: 'app-goods-form',
templateUrl: './goods-form.component.html',
styleUrl: './goods-form.component.scss',
})
export class GoodsFormComponent {
@Input() product!: Goods;
@Output() productChange = new EventEmitter<Goods>();
@Output() isUpdate = new EventEmitter<boolean>();
form: FormGroup;
constructor(
fb: FormBuilder,
private goodsService: GoodsService,
) {
this.form = fb.group({
name: ['', Validators.required],
price: ['', Validators.required],
});
}
public get isNew(): boolean {
return !this.product;
}
ngOnInit(): void {
if (!this.isNew) {
this.form.setValue({
name: this.product?.name,
price: this.product?.price,
});
}
}
save() {
if (this.form.valid) {
if (this.isNew) {
// Create
this.goodsService.post({ ...this.form.value }).subscribe({
next: (res) => {
this.isUpdate.emit(true);
},
error: (err) => {
if (DEV) {
console.dir(err);
}
},
});
} else {
// Update
this.goodsService
.put({ ...this.form.value, id: this.product.id })
.subscribe({
next: (res) => {
this.productChange.emit(res);
this.isUpdate.emit(true);
},
error: (err) => {
if (DEV) {
console.dir(err);
}
},
});
}
}
}
}
<form [formGroup]="form">
<div class="mb-3">
<div class="mb-3">
<label for="clientInput" class="form-label">Client</label>
<input
class="form-control"
id="clientInput"
placeholder="Client Name"
formControlName="client"
[class.is-invalid]="form.touched && form.get('client')?.invalid"
/>
<div class="invalid-feedback">
<app-form-errors
[control]="form.get('client')!"
[messages]="validationMsgs['client']"
></app-form-errors>
</div>
</div>
<div class="mb-3">
<label for="addressInput" class="form-label">Address</label>
<input
class="form-control"
id="addressInput"
placeholder="Address line"
formControlName="address"
[class.is-invalid]="form.touched && form.get('address')?.invalid"
/>
<div class="invalid-feedback">
<app-form-errors
[control]="form.get('address')!"
[messages]="validationMsgs['address']"
></app-form-errors>
</div>
</div>
<div class="mb-3">
<label for="dateInput" class="form-label">Date</label>
<div class="input-group">
<input
class="form-control"
placeholder="dd/mm/yyyy"
type="text"
ngbDatepicker
#d="ngbDatepicker"
id="addressInput"
formControlName="date"
[class.is-invalid]="form.touched && form.get('date')?.invalid"
/>
<button
class="btn btn-outline-secondary bi bi-calendar3"
(click)="d.toggle()"
type="button"
></button>
</div>
<div class="invalid-feedback">
<app-form-errors
[control]="form.get('date')!"
[messages]="validationMsgs['date']"
></app-form-errors>
</div>
</div>
</div>
<button class="btn btn-outline-primary me-2" (click)="save()">
{{ isNew ? "Create" : "Update" }}
</button>
</form>
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { OrderFormComponent } from './order-form.component';
describe('OrderFormComponent', () => {
let component: OrderFormComponent;
let fixture: ComponentFixture<OrderFormComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [OrderFormComponent]
})
.compileComponents();
fixture = TestBed.createComponent(OrderFormComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { environment } from '../../../../environments/environment';
import { Order } from '../../../core/model/order.model';
import { ControlsValidations } from '../../../core/model/validation.model';
import { OrderService } from '../../../core/service/order.service';
const DEV = !environment.production;
@Component({
selector: 'app-order-form',
templateUrl: './order-form.component.html',
styleUrl: './order-form.component.scss',
})
export class OrderFormComponent {
@Input() order!: Order;
@Output() orderChange = new EventEmitter<Order>();
form: FormGroup;
validationMsgs: ControlsValidations;
constructor(
fb: FormBuilder,
private router: Router,
private orderService: OrderService,
) {
this.form = fb.group({
client: ['', Validators.required],
address: ['', Validators.required],
date: ['', Validators.required],
});
this.validationMsgs = {
client: [{ type: 'required', message: 'Required fileld' }],
address: [{ type: 'required', message: 'Required fileld' }],
date: [
{ type: 'required', message: 'Required fileld' },
{ type: 'ngbDate', message: 'Invalid date format' },
],
};
}
public get isNew(): boolean {
return !this.order;
}
save() {
if (this.form.valid) {
if (this.isNew) {
this.orderService.postOrder({ ...this.form.value }).subscribe({
next: (res) => {
this.orderChange.emit(res);
this.router.navigate(['order', res.id]);
},
error: (err) => {
if (DEV) {
console.dir(err);
}
},
});
} else {
this.orderService
.putOrder({ ...this.form.value, id: this.order.id })
.subscribe({
next: (res) => {
this.orderChange.emit(res);
},
error: (err) => {
if (DEV) {
console.dir(err);
}
},
});
}
}
}
ngOnInit(): void {
if (!this.isNew) {
this.form.setValue({
client: this.order.client,
date: this.order.date,
address: this.order.address,
});
}
}
}
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import {
NgbDatepickerModule,
NgbNav,
NgbNavContent,
NgbNavItem,
NgbNavItemRole,
NgbNavLinkBase,
NgbNavLinkButton,
NgbNavOutlet,
NgbPagination,
} from '@ng-bootstrap/ng-bootstrap';
import { FormErrorsComponent } from './form-errors/form-errors.component';
import { GoodsFormComponent } from './forms/goods-form/goods-form.component';
import { OrderFormComponent } from './forms/order-form/order-form.component';
@NgModule({ @NgModule({
declarations: [], declarations: [FormErrorsComponent, OrderFormComponent, GoodsFormComponent],
imports: [ imports: [
CommonModule CommonModule,
] FormsModule,
ReactiveFormsModule,
NgbNav,
NgbNavContent,
NgbNavItem,
NgbNavItemRole,
NgbNavLinkBase,
NgbNavLinkButton,
NgbNavOutlet,
NgbDatepickerModule,
NgbPagination,
],
exports: [
FormsModule,
ReactiveFormsModule,
NgbNav,
NgbNavContent,
NgbNavItem,
NgbNavItemRole,
NgbNavLinkBase,
NgbNavLinkButton,
NgbNavOutlet,
NgbDatepickerModule,
NgbPagination,
FormErrorsComponent,
OrderFormComponent,
GoodsFormComponent,
],
}) })
export class SharedModule { } export class SharedModule {}
...@@ -5,5 +5,6 @@ export const environment = { ...@@ -5,5 +5,6 @@ export const environment = {
API_PREFIX: '/api', API_PREFIX: '/api',
ORDER_URL: '/orders', ORDER_URL: '/orders',
ORDER_LINE_URL: '/order-line',
GOODS_URL: '/goods', GOODS_URL: '/goods',
}; };
...@@ -5,5 +5,6 @@ export const environment = { ...@@ -5,5 +5,6 @@ export const environment = {
API_PREFIX: '/api', API_PREFIX: '/api',
ORDER_URL: '/orders', ORDER_URL: '/orders',
ORDER_LINE_URL: '/order-line',
GOODS_URL: '/goods', GOODS_URL: '/goods',
}; };
/// <reference types="@angular/localize" />
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module'; import { AppModule } from './app/app.module';
......
/* You can add global styles to this file, and also import other style files */ /* You can add global styles to this file, and also import other style files */
/* Importing Bootstrap SCSS file. */
@import "bootstrap/scss/bootstrap";
...@@ -3,7 +3,9 @@ ...@@ -3,7 +3,9 @@
"extends": "./tsconfig.json", "extends": "./tsconfig.json",
"compilerOptions": { "compilerOptions": {
"outDir": "./out-tsc/app", "outDir": "./out-tsc/app",
"types": [] "types": [
"@angular/localize"
]
}, },
"files": [ "files": [
"src/main.ts" "src/main.ts"
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
"sourceMap": true, "sourceMap": true,
"declaration": false, "declaration": false,
"experimentalDecorators": true, "experimentalDecorators": true,
"moduleResolution": "bundler", "moduleResolution": "node",
"importHelpers": true, "importHelpers": true,
"target": "ES2022", "target": "ES2022",
"module": "ES2022", "module": "ES2022",
......
...@@ -4,7 +4,8 @@ ...@@ -4,7 +4,8 @@
"compilerOptions": { "compilerOptions": {
"outDir": "./out-tsc/spec", "outDir": "./out-tsc/spec",
"types": [ "types": [
"jasmine" "jasmine",
"@angular/localize"
] ]
}, },
"include": [ "include": [
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment