Feature: The guestbook now autocompletes on phone number

This commit is contained in:
Amritanshu Agrawal 2021-03-26 08:53:17 +05:30
parent 3705ceb95b
commit d5b4dfeaca
11 changed files with 97 additions and 9 deletions

View File

@ -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"])

View File

@ -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,

View File

@ -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"}

View File

@ -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:

View 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);
}
}

View File

@ -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"

View File

@ -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;

View File

@ -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] },

View File

@ -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[]
>;
}
}

View File

@ -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',

View File

@ -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