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.session import engine
|
||||
from .routers import (
|
||||
customer,
|
||||
device,
|
||||
guest_book,
|
||||
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(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(save.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()]
|
||||
|
||||
|
||||
@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)
|
||||
def show_id(
|
||||
id_: uuid.UUID,
|
||||
|
@ -10,7 +10,7 @@ from . import to_camel
|
||||
class CustomerIn(BaseModel):
|
||||
name: str = Field(..., min_length=1)
|
||||
phone: str = Field(..., min_length=1)
|
||||
address: str
|
||||
address: Optional[str]
|
||||
|
||||
class Config:
|
||||
fields = {"id_": "id"}
|
||||
|
@ -11,7 +11,7 @@ from . import to_camel
|
||||
class GuestBookIn(BaseModel):
|
||||
name: str
|
||||
phone: str
|
||||
address: str
|
||||
address: Optional[str]
|
||||
pax: int = Field(ge=0)
|
||||
|
||||
class Config:
|
||||
|
12
bookie/src/app/guest-book/customer.ts
Normal file
12
bookie/src/app/guest-book/customer.ts
Normal file
@ -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-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-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
|
||||
fxLayout="row"
|
||||
|
@ -1,20 +1,26 @@
|
||||
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 { Observable, of as observableOf } from 'rxjs';
|
||||
import { debounceTime, distinctUntilChanged, map, startWith, switchMap } from 'rxjs/operators';
|
||||
|
||||
import { ToasterService } from '../../core/toaster.service';
|
||||
import { Customer } from '../customer';
|
||||
import { GuestBook } from '../guest-book';
|
||||
import { GuestBookService } from '../guest-book.service';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-guest-book-detail',
|
||||
templateUrl: './guest-book-detail.component.html',
|
||||
styleUrls: ['./guest-book-detail.component.css'],
|
||||
})
|
||||
export class GuestBookDetailComponent implements OnInit, AfterViewInit {
|
||||
@ViewChild('name', { static: true }) nameElement?: ElementRef;
|
||||
@ViewChild('phone', { static: true }) phoneElement?: ElementRef;
|
||||
form: FormGroup;
|
||||
item: GuestBook = new GuestBook();
|
||||
customers: Observable<Customer[]>;
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
@ -30,6 +36,14 @@ export class GuestBookDetailComponent implements OnInit, AfterViewInit {
|
||||
pax: ['0', Validators.required],
|
||||
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() {
|
||||
@ -41,8 +55,8 @@ export class GuestBookDetailComponent implements OnInit, AfterViewInit {
|
||||
|
||||
ngAfterViewInit() {
|
||||
setTimeout(() => {
|
||||
if (this.nameElement !== undefined) {
|
||||
this.nameElement.nativeElement.focus();
|
||||
if (this.phoneElement !== undefined) {
|
||||
this.phoneElement.nativeElement.focus();
|
||||
}
|
||||
}, 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 {
|
||||
const formModel = this.form.value;
|
||||
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.address = formModel.address;
|
||||
return this.item;
|
||||
|
@ -3,6 +3,7 @@ import { NgModule } from '@angular/core';
|
||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { MomentDateAdapter } from '@angular/material-moment-adapter';
|
||||
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import {
|
||||
@ -53,6 +54,7 @@ export const MY_FORMATS = {
|
||||
FlexLayoutModule,
|
||||
GuestBookRoutingModule,
|
||||
SharedModule,
|
||||
MatAutocompleteModule,
|
||||
],
|
||||
providers: [
|
||||
{ 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 { Customer } from './customer';
|
||||
import { GuestBook } from './guest-book';
|
||||
import { GuestBookList } from './guest-book-list';
|
||||
|
||||
@ -13,6 +14,7 @@ const httpOptions = {
|
||||
};
|
||||
|
||||
const url = '/api/guest-book';
|
||||
const customerUrl = '/api/customers';
|
||||
const serviceName = 'GuestBookService';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
@ -57,4 +59,13 @@ export class GuestBookService {
|
||||
.delete<GuestBook>(`${url}/${id}`, httpOptions)
|
||||
.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';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-product-list',
|
||||
templateUrl: './product-list.component.html',
|
||||
|
@ -21,6 +21,7 @@
|
||||
formControlName="name"
|
||||
(keyup.enter)="save()"
|
||||
/>
|
||||
<mat-hint> Format for GST: ST GST @ x% (1/2) ; CGST @ x% (1/2) </mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div
|
||||
|
Loading…
Reference in New Issue
Block a user