Feature: The guestbook now autocompletes on phone number
This commit is contained in:
parent
3705ceb95b
commit
d5b4dfeaca
|
@ -7,6 +7,7 @@ from .core.config import settings
|
||||||
from .db.base_class import Base
|
from .db.base_class import Base
|
||||||
from .db.session import engine
|
from .db.session import engine
|
||||||
from .routers import (
|
from .routers import (
|
||||||
|
customer,
|
||||||
device,
|
device,
|
||||||
guest_book,
|
guest_book,
|
||||||
header_footer,
|
header_footer,
|
||||||
|
@ -97,6 +98,7 @@ app.include_router(sale_report.router, prefix="/api/sale-report", tags=["reports
|
||||||
app.include_router(tax_report.router, prefix="/api/tax-report", tags=["reports"])
|
app.include_router(tax_report.router, prefix="/api/tax-report", tags=["reports"])
|
||||||
|
|
||||||
app.include_router(guest_book.router, prefix="/api/guest-book", tags=["guest-book"])
|
app.include_router(guest_book.router, prefix="/api/guest-book", tags=["guest-book"])
|
||||||
|
app.include_router(customer.router, prefix="/api/customers", tags=["guest-book"])
|
||||||
app.include_router(show.router, prefix="/api/voucher", tags=["voucher"])
|
app.include_router(show.router, prefix="/api/voucher", tags=["voucher"])
|
||||||
app.include_router(save.router, prefix="/api/voucher", tags=["voucher"])
|
app.include_router(save.router, prefix="/api/voucher", tags=["voucher"])
|
||||||
app.include_router(update.router, prefix="/api/voucher", tags=["voucher"])
|
app.include_router(update.router, prefix="/api/voucher", tags=["voucher"])
|
||||||
|
|
|
@ -106,6 +106,18 @@ def show_list(db: Session = Depends(get_db), user: UserToken = Depends(get_user)
|
||||||
return [customer_info(item) for item in db.query(Customer).order_by(Customer.name).all()]
|
return [customer_info(item) for item in db.query(Customer).order_by(Customer.name).all()]
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/query", response_model=List[schemas.Customer])
|
||||||
|
async def show_term(
|
||||||
|
q: str,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: UserToken = Depends(get_user),
|
||||||
|
) -> List[schemas.Customer]:
|
||||||
|
return [
|
||||||
|
customer_info(item)
|
||||||
|
for item in db.query(Customer).filter(Customer.phone.ilike(f"%{q}%")).order_by(Customer.name).all()
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{id_}", response_model=schemas.Customer)
|
@router.get("/{id_}", response_model=schemas.Customer)
|
||||||
def show_id(
|
def show_id(
|
||||||
id_: uuid.UUID,
|
id_: uuid.UUID,
|
||||||
|
|
|
@ -10,7 +10,7 @@ from . import to_camel
|
||||||
class CustomerIn(BaseModel):
|
class CustomerIn(BaseModel):
|
||||||
name: str = Field(..., min_length=1)
|
name: str = Field(..., min_length=1)
|
||||||
phone: str = Field(..., min_length=1)
|
phone: str = Field(..., min_length=1)
|
||||||
address: str
|
address: Optional[str]
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
fields = {"id_": "id"}
|
fields = {"id_": "id"}
|
||||||
|
|
|
@ -11,7 +11,7 @@ from . import to_camel
|
||||||
class GuestBookIn(BaseModel):
|
class GuestBookIn(BaseModel):
|
||||||
name: str
|
name: str
|
||||||
phone: str
|
phone: str
|
||||||
address: str
|
address: Optional[str]
|
||||||
pax: int = Field(ge=0)
|
pax: int = Field(ge=0)
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
export class Customer {
|
||||||
|
name: string;
|
||||||
|
phone: string;
|
||||||
|
address: string;
|
||||||
|
|
||||||
|
public constructor(init?: Partial<Customer>) {
|
||||||
|
this.name = '';
|
||||||
|
this.phone = '';
|
||||||
|
this.address = '';
|
||||||
|
Object.assign(this, init);
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,8 +29,26 @@
|
||||||
>
|
>
|
||||||
<mat-form-field fxFlex>
|
<mat-form-field fxFlex>
|
||||||
<mat-label>Phone</mat-label>
|
<mat-label>Phone</mat-label>
|
||||||
<input matInput #phone placeholder="Phone" type="text" formControlName="phone" />
|
<input
|
||||||
|
matInput
|
||||||
|
#phone
|
||||||
|
placeholder="Phone"
|
||||||
|
type="text"
|
||||||
|
formControlName="phone"
|
||||||
|
[matAutocomplete]="auto"
|
||||||
|
autocomplete="off"
|
||||||
|
/>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
<mat-autocomplete
|
||||||
|
#auto="matAutocomplete"
|
||||||
|
autoActiveFirstOption
|
||||||
|
[displayWith]="displayFn"
|
||||||
|
(optionSelected)="selected($event)"
|
||||||
|
>
|
||||||
|
<mat-option *ngFor="let customer of customers | async" [value]="customer"
|
||||||
|
>{{ customer.name }} - {{ customer.phone }}</mat-option
|
||||||
|
>
|
||||||
|
</mat-autocomplete>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
fxLayout="row"
|
fxLayout="row"
|
||||||
|
|
|
@ -1,20 +1,26 @@
|
||||||
import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core';
|
import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core';
|
||||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
|
||||||
|
import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
|
import { debounceTime, distinctUntilChanged, map, startWith, switchMap } from 'rxjs/operators';
|
||||||
|
|
||||||
import { ToasterService } from '../../core/toaster.service';
|
import { ToasterService } from '../../core/toaster.service';
|
||||||
|
import { Customer } from '../customer';
|
||||||
import { GuestBook } from '../guest-book';
|
import { GuestBook } from '../guest-book';
|
||||||
import { GuestBookService } from '../guest-book.service';
|
import { GuestBookService } from '../guest-book.service';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-guest-book-detail',
|
selector: 'app-guest-book-detail',
|
||||||
templateUrl: './guest-book-detail.component.html',
|
templateUrl: './guest-book-detail.component.html',
|
||||||
styleUrls: ['./guest-book-detail.component.css'],
|
styleUrls: ['./guest-book-detail.component.css'],
|
||||||
})
|
})
|
||||||
export class GuestBookDetailComponent implements OnInit, AfterViewInit {
|
export class GuestBookDetailComponent implements OnInit, AfterViewInit {
|
||||||
@ViewChild('name', { static: true }) nameElement?: ElementRef;
|
@ViewChild('phone', { static: true }) phoneElement?: ElementRef;
|
||||||
form: FormGroup;
|
form: FormGroup;
|
||||||
item: GuestBook = new GuestBook();
|
item: GuestBook = new GuestBook();
|
||||||
|
customers: Observable<Customer[]>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private fb: FormBuilder,
|
private fb: FormBuilder,
|
||||||
|
@ -30,6 +36,14 @@ export class GuestBookDetailComponent implements OnInit, AfterViewInit {
|
||||||
pax: ['0', Validators.required],
|
pax: ['0', Validators.required],
|
||||||
address: null,
|
address: null,
|
||||||
});
|
});
|
||||||
|
// Setup Account Autocomplete
|
||||||
|
this.customers = (this.form.get('phone') as FormControl).valueChanges.pipe(
|
||||||
|
startWith(null),
|
||||||
|
map((x) => (x !== null && x.length >= 1 ? x : null)),
|
||||||
|
debounceTime(150),
|
||||||
|
distinctUntilChanged(),
|
||||||
|
switchMap((x) => (x === null ? observableOf([]) : this.ser.autocomplete(x))),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
@ -41,8 +55,8 @@ export class GuestBookDetailComponent implements OnInit, AfterViewInit {
|
||||||
|
|
||||||
ngAfterViewInit() {
|
ngAfterViewInit() {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this.nameElement !== undefined) {
|
if (this.phoneElement !== undefined) {
|
||||||
this.nameElement.nativeElement.focus();
|
this.phoneElement.nativeElement.focus();
|
||||||
}
|
}
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
@ -69,10 +83,27 @@ export class GuestBookDetailComponent implements OnInit, AfterViewInit {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
displayFn(customer?: Customer): string {
|
||||||
|
return customer ? customer.phone : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
selected(event: MatAutocompleteSelectedEvent): void {
|
||||||
|
const customer = event.option.value;
|
||||||
|
this.form.patchValue({
|
||||||
|
name: customer.name,
|
||||||
|
// phone: customer.phone,
|
||||||
|
address: customer.address,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
getItem(): GuestBook {
|
getItem(): GuestBook {
|
||||||
const formModel = this.form.value;
|
const formModel = this.form.value;
|
||||||
this.item.name = formModel.name;
|
this.item.name = formModel.name;
|
||||||
this.item.phone = formModel.phone;
|
if (typeof formModel.phone === 'string') {
|
||||||
|
this.item.phone = formModel.phone;
|
||||||
|
} else {
|
||||||
|
this.item.phone = (formModel.phone as Customer).phone;
|
||||||
|
}
|
||||||
this.item.pax = parseInt(formModel.pax, 10);
|
this.item.pax = parseInt(formModel.pax, 10);
|
||||||
this.item.address = formModel.address;
|
this.item.address = formModel.address;
|
||||||
return this.item;
|
return this.item;
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { NgModule } from '@angular/core';
|
||||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||||
import { ReactiveFormsModule } from '@angular/forms';
|
import { ReactiveFormsModule } from '@angular/forms';
|
||||||
import { MomentDateAdapter } from '@angular/material-moment-adapter';
|
import { MomentDateAdapter } from '@angular/material-moment-adapter';
|
||||||
|
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatCardModule } from '@angular/material/card';
|
import { MatCardModule } from '@angular/material/card';
|
||||||
import {
|
import {
|
||||||
|
@ -53,6 +54,7 @@ export const MY_FORMATS = {
|
||||||
FlexLayoutModule,
|
FlexLayoutModule,
|
||||||
GuestBookRoutingModule,
|
GuestBookRoutingModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
|
MatAutocompleteModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE] },
|
{ provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE] },
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { catchError } from 'rxjs/operators';
|
||||||
|
|
||||||
import { ErrorLoggerService } from '../core/error-logger.service';
|
import { ErrorLoggerService } from '../core/error-logger.service';
|
||||||
|
|
||||||
|
import { Customer } from './customer';
|
||||||
import { GuestBook } from './guest-book';
|
import { GuestBook } from './guest-book';
|
||||||
import { GuestBookList } from './guest-book-list';
|
import { GuestBookList } from './guest-book-list';
|
||||||
|
|
||||||
|
@ -13,6 +14,7 @@ const httpOptions = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const url = '/api/guest-book';
|
const url = '/api/guest-book';
|
||||||
|
const customerUrl = '/api/customers';
|
||||||
const serviceName = 'GuestBookService';
|
const serviceName = 'GuestBookService';
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
|
@ -57,4 +59,13 @@ export class GuestBookService {
|
||||||
.delete<GuestBook>(`${url}/${id}`, httpOptions)
|
.delete<GuestBook>(`${url}/${id}`, httpOptions)
|
||||||
.pipe(catchError(this.log.handleError(serviceName, 'delete'))) as Observable<GuestBook>;
|
.pipe(catchError(this.log.handleError(serviceName, 'delete'))) as Observable<GuestBook>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
autocomplete(query: string): Observable<Customer[]> {
|
||||||
|
const options = { params: new HttpParams().set('q', query) };
|
||||||
|
return this.http
|
||||||
|
.get<Customer[]>(`${customerUrl}/query`, options)
|
||||||
|
.pipe(catchError(this.log.handleError(serviceName, 'autocomplete'))) as Observable<
|
||||||
|
Customer[]
|
||||||
|
>;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@ import { ProductService } from '../product.service';
|
||||||
|
|
||||||
import { ProductListDataSource } from './product-list-datasource';
|
import { ProductListDataSource } from './product-list-datasource';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-product-list',
|
selector: 'app-product-list',
|
||||||
templateUrl: './product-list.component.html',
|
templateUrl: './product-list.component.html',
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
formControlName="name"
|
formControlName="name"
|
||||||
(keyup.enter)="save()"
|
(keyup.enter)="save()"
|
||||||
/>
|
/>
|
||||||
|
<mat-hint> Format for GST: ST GST @ x% (1/2) ; CGST @ x% (1/2) </mat-hint>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|
Loading…
Reference in New Issue