Commit 699282d6 authored by Vladimir Trubachoff's avatar Vladimir Trubachoff

Frontend: fixes date format and validation

Backend: added LocalDate (de-)serializer
parent 9aba07ea
package com.example.testj.serializer;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
public class CustomLocalDateDeserializer extends JsonDeserializer<LocalDate> {
@Override
public LocalDate deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException {
String dateAsString = jsonParser.getText();
if (dateAsString.isEmpty()) return null;
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-M-d");
return LocalDate.parse(dateAsString, formatter);
}
}
\ No newline at end of file
package com.example.testj.serializer;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
public class CustomLocalDateSerializer extends JsonSerializer<LocalDate> {
@Override
public void serialize(LocalDate value, JsonGenerator generator, SerializerProvider provider) throws IOException {
if (value == null) {
generator.writeNull();
} else {
generator.writeString(value.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
}
}
}
\ No newline at end of file
package com.example.testj.service.dto; package com.example.testj.service.dto;
import com.example.testj.serializer.CustomLocalDateDeserializer;
import com.example.testj.serializer.CustomLocalDateSerializer;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
...@@ -10,7 +14,7 @@ import org.springframework.format.annotation.DateTimeFormat; ...@@ -10,7 +14,7 @@ import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable; import java.io.Serializable;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List; import java.util.Set;
/** /**
* DTO for {@link com.example.testj.domain.Order} * DTO for {@link com.example.testj.domain.Order}
...@@ -26,11 +30,13 @@ public class OrderDto implements Serializable { ...@@ -26,11 +30,13 @@ public class OrderDto implements Serializable {
private String client; private String client;
@NotNull @NotNull
@JsonDeserialize(using = CustomLocalDateDeserializer.class)
@JsonSerialize(using = CustomLocalDateSerializer.class)
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
private LocalDate date; private LocalDate date;
@NotBlank @NotBlank
private String address; private String address;
private List<OrderLineDto> orderLines; private Set<OrderLineDto> orderLines;
} }
\ No newline at end of file
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { provideHttpClient } from '@angular/common/http'; import { provideHttpClient } from '@angular/common/http';
import { import {
NgbDateAdapter, NgbDateAdapter,
NgbDateParserFormatter, NgbDateParserFormatter,
NgbModule, NgbModule,
} from '@ng-bootstrap/ng-bootstrap'; } from '@ng-bootstrap/ng-bootstrap';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { import {
CustomDateAdapter, CustomDateAdapter,
CustomDateParserFormatter, CustomDateParserFormatter,
......
...@@ -9,14 +9,14 @@ import { ...@@ -9,14 +9,14 @@ import {
*/ */
@Injectable() @Injectable()
export class CustomDateAdapter { export class CustomDateAdapter {
readonly DELIMITER = '/'; readonly DELIMITER = '-';
fromModel(value: string | null): NgbDateStruct | null { fromModel(value: string | null): NgbDateStruct | null {
if (value) { if (value) {
const date = value.split(this.DELIMITER); const date = value.split(this.DELIMITER);
return { return {
day: parseInt(date[0], 10), day: parseInt(date[2], 10),
month: parseInt(date[1], 10), month: parseInt(date[1], 10),
year: parseInt(date[2], 10), year: parseInt(date[0], 10),
}; };
} }
return null; return null;
...@@ -24,7 +24,11 @@ export class CustomDateAdapter { ...@@ -24,7 +24,11 @@ export class CustomDateAdapter {
toModel(date: NgbDateStruct | null): string | null { toModel(date: NgbDateStruct | null): string | null {
return date return date
? date.day + this.DELIMITER + date.month + this.DELIMITER + date.year ? date.year +
this.DELIMITER +
('0' + date.month).slice(-2) +
this.DELIMITER +
('0' + date.day).slice(-2)
: null; : null;
} }
} }
...@@ -34,7 +38,7 @@ export class CustomDateAdapter { ...@@ -34,7 +38,7 @@ export class CustomDateAdapter {
*/ */
@Injectable() @Injectable()
export class CustomDateParserFormatter extends NgbDateParserFormatter { export class CustomDateParserFormatter extends NgbDateParserFormatter {
readonly DELIMITER = '/'; readonly DELIMITER = '-';
parse(value: string): NgbDateStruct | null { parse(value: string): NgbDateStruct | null {
if (value) { if (value) {
...@@ -50,7 +54,11 @@ export class CustomDateParserFormatter extends NgbDateParserFormatter { ...@@ -50,7 +54,11 @@ export class CustomDateParserFormatter extends NgbDateParserFormatter {
format(date: NgbDateStruct | null): string { format(date: NgbDateStruct | null): string {
return date return date
? date.day + this.DELIMITER + date.month + this.DELIMITER + date.year ? date.day +
this.DELIMITER +
('0' + date.month).slice(-2) +
this.DELIMITER +
date.year
: ''; : '';
} }
} }
import { Goods } from './goods.model';
export interface OrderLine {
id: number;
goods: Goods;
count: number;
}
export interface OrderLineCreateReq {
orderId: number;
goodsId: number;
count: number;
}
export interface OrderLineUpdateReq {
count: number;
}
import { Goods } from './goods.model'; import { OrderLine } from './order-line.model';
export interface Order { export interface Order {
id: number; id: number;
...@@ -7,16 +7,3 @@ export interface Order { ...@@ -7,16 +7,3 @@ export interface Order {
date: string; date: string;
orderLines: OrderLine[]; orderLines: OrderLine[];
} }
export interface OrderLine {
id: number;
goods: Goods;
count: number;
}
export interface OrderLineReq {
id?: number;
orderId: number;
goodsId: number;
count: number;
}
...@@ -2,7 +2,10 @@ import { HttpClient, HttpErrorResponse } from '@angular/common/http'; ...@@ -2,7 +2,10 @@ import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { catchError, EMPTY } from 'rxjs'; import { catchError, EMPTY } from 'rxjs';
import { environment } from '../../../environments/environment'; import { environment } from '../../../environments/environment';
import { OrderLine, OrderLineReq } from '../model/order.model'; import {
OrderLineCreateReq,
OrderLineUpdateReq,
} from '../model/order-line.model';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
...@@ -12,14 +15,14 @@ export class OrderLineService { ...@@ -12,14 +15,14 @@ export class OrderLineService {
constructor(private http: HttpClient) {} constructor(private http: HttpClient) {}
put(ol: OrderLineReq) { put(id: number, req: OrderLineUpdateReq) {
const url = `${this.apiUrl}/${ol.id}`; const url = `${this.apiUrl}/${id}`;
return this.http.put<OrderLine>(url, ol); return this.http.put<OrderLineUpdateReq>(url, req);
} }
post(ol: OrderLineReq) { post(req: OrderLineCreateReq) {
const url = this.apiUrl; const url = this.apiUrl;
return this.http.post<OrderLine>(url, ol); return this.http.post<OrderLineCreateReq>(url, req);
} }
delete(id: number) { delete(id: number) {
...@@ -28,7 +31,7 @@ export class OrderLineService { ...@@ -28,7 +31,7 @@ export class OrderLineService {
catchError((err: HttpErrorResponse) => { catchError((err: HttpErrorResponse) => {
console.dir(err); console.dir(err);
return EMPTY; return EMPTY;
}) }),
); );
} }
} }
...@@ -16,7 +16,7 @@ export class AddProductModalComponent extends AbstractBasicComponent { ...@@ -16,7 +16,7 @@ export class AddProductModalComponent extends AbstractBasicComponent {
@Input() goods!: PagedResponseModel<Goods>; @Input() goods!: PagedResponseModel<Goods>;
@Input() order!: Order; @Input() order!: Order;
@Output() pageNumber = new EventEmitter<number>(); @Output() pageNumber = new EventEmitter<number>();
@Output() orderLineChange = new EventEmitter<UpdateCount>(); @Output() countChange = new EventEmitter<UpdateCount>();
modal: NgbModalRef | undefined; modal: NgbModalRef | undefined;
...@@ -32,7 +32,7 @@ export class AddProductModalComponent extends AbstractBasicComponent { ...@@ -32,7 +32,7 @@ export class AddProductModalComponent extends AbstractBasicComponent {
addOrUpdate(goodsId: number, count: number) { addOrUpdate(goodsId: number, count: number) {
if (count) { if (count) {
this.orderLineChange.emit({ this.countChange.emit({
goodsId, goodsId,
count, count,
}); });
......
import { Component, EventEmitter, Input, Output } from '@angular/core'; import { Component, EventEmitter, Input, Output } from '@angular/core';
import { OrderLine } from '../../../../core/model/order.model'; import { OrderLine } from '../../../../core/model/order-line.model';
@Component({ @Component({
selector: 'app-order-line-list', selector: 'app-order-line-list',
...@@ -8,9 +8,9 @@ import { OrderLine } from '../../../../core/model/order.model'; ...@@ -8,9 +8,9 @@ import { OrderLine } from '../../../../core/model/order.model';
}) })
export class OrderLineListComponent { export class OrderLineListComponent {
@Input() orderLines!: OrderLine[]; @Input() orderLines!: OrderLine[];
@Output() delete = new EventEmitter<number>(); @Output() deleteId = new EventEmitter<number>();
deleteLine(id: number) { deleteLine(id: number) {
this.delete.emit(id); this.deleteId.emit(id);
} }
} }
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
</tr> </tr>
<tr> <tr>
<th>Date</th> <th>Date</th>
<td>{{ order.date }}</td> <td>{{ order.date | date }}</td>
</tr> </tr>
</table> </table>
...@@ -34,13 +34,12 @@ ...@@ -34,13 +34,12 @@
class="btn btn-outline-secondary me-2" class="btn btn-outline-secondary me-2"
(click)="openAddProductModal()" (click)="openAddProductModal()"
> >
Edit products Add products
</button> </button>
</div> </div>
<app-order-line-list <app-order-line-list
(delete)="deleteLine($event)" (deleteId)="deleteLine($event)"
(add)="openAddProductModal()"
[orderLines]="order.orderLines" [orderLines]="order.orderLines"
></app-order-line-list> ></app-order-line-list>
} @else { } @else {
......
...@@ -103,7 +103,7 @@ export class OrderPageComponent ...@@ -103,7 +103,7 @@ export class OrderPageComponent
orderModalRef.componentInstance.goods = resp; orderModalRef.componentInstance.goods = resp;
}); });
orderModalRef.componentInstance.orderLineChange orderModalRef.componentInstance.countChange
.pipe( .pipe(
takeUntil(this.destroy$), takeUntil(this.destroy$),
concatMap((v: UpdateCount) => { concatMap((v: UpdateCount) => {
...@@ -111,12 +111,7 @@ export class OrderPageComponent ...@@ -111,12 +111,7 @@ export class OrderPageComponent
(ol) => ol.goods.id === v.goodsId, (ol) => ol.goods.id === v.goodsId,
)?.id; )?.id;
if (orderLineId) { if (orderLineId) {
return this.orderLineService.put({ return this.orderLineService.put(orderLineId, { count: v.count });
id: orderLineId,
orderId: this.order.id,
goodsId: v.goodsId,
count: v.count,
});
} }
return this.orderLineService.post({ return this.orderLineService.post({
orderId: this.order.id, orderId: this.order.id,
......
...@@ -34,14 +34,14 @@ ...@@ -34,14 +34,14 @@
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="dateInput" class="form-label">Date</label> <label for="dateInput" class="form-label">Date</label>
<div class="input-group"> <div class="input-group has-validation">
<input <input
class="form-control" class="form-control"
placeholder="dd/mm/yyyy" placeholder="d-mm-yyyy"
type="text" type="text"
ngbDatepicker ngbDatepicker
#d="ngbDatepicker" #d="ngbDatepicker"
id="addressInput" id="dateInput"
formControlName="date" formControlName="date"
[class.is-invalid]="form.touched && form.get('date')?.invalid" [class.is-invalid]="form.touched && form.get('date')?.invalid"
/> />
...@@ -50,12 +50,12 @@ ...@@ -50,12 +50,12 @@
(click)="d.toggle()" (click)="d.toggle()"
type="button" type="button"
></button> ></button>
</div> <div class="invalid-feedback">
<div class="invalid-feedback"> <app-form-errors
<app-form-errors [control]="form.get('date')!"
[control]="form.get('date')!" [messages]="validationMsgs['date']"
[messages]="validationMsgs['date']" ></app-form-errors>
></app-form-errors> </div>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -46,7 +46,7 @@ export class OrderFormComponent { ...@@ -46,7 +46,7 @@ export class OrderFormComponent {
} }
save() { save() {
if (this.form.valid) { if (this.form.valid && this.form.touched) {
if (this.isNew) { if (this.isNew) {
this.orderService.postOrder({ ...this.form.value }).subscribe({ this.orderService.postOrder({ ...this.form.value }).subscribe({
next: (res) => { next: (res) => {
......
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