mirror of
				https://github.com/ae-utbm/sith.git
				synced 2025-10-31 00:53:08 +00:00 
			
		
		
		
	Make counter click client side first
This commit is contained in:
		| @@ -15,6 +15,10 @@ export class CounterProductSelect extends AutoCompleteSelectBase { | ||||
|     return ["FIN", "ANN"]; | ||||
|   } | ||||
|  | ||||
|   public getSelectedProduct(): [number, string] { | ||||
|     return parseProduct(this.widget.getValue() as string); | ||||
|   } | ||||
|  | ||||
|   protected attachBehaviors(): void { | ||||
|     this.allowMultipleProducts(); | ||||
|   } | ||||
|   | ||||
| @@ -1,24 +1,47 @@ | ||||
| import { exportToHtml } from "#core:utils/globals"; | ||||
| import type TomSelect from "tom-select"; | ||||
|  | ||||
| const quantityForTrayPrice = 6; | ||||
|  | ||||
| interface CounterConfig { | ||||
|   csrfToken: string; | ||||
|   clickApiUrl: string; | ||||
|   sessionBasket: Record<number, BasketItem>; | ||||
|   customerBalance: number; | ||||
|   customerId: number; | ||||
|   products: Record<string, Product>; | ||||
| } | ||||
| interface BasketItem { | ||||
|   // biome-ignore lint/style/useNamingConvention: talking with python | ||||
|   bonus_qty: number; | ||||
|  | ||||
| interface Product { | ||||
|   code: string; | ||||
|   name: string; | ||||
|   price: number; | ||||
|   qty: number; | ||||
|   hasTrayPrice: boolean; | ||||
| } | ||||
|  | ||||
| class BasketItem { | ||||
|   quantity: number; | ||||
|   product: Product; | ||||
|  | ||||
|   constructor(product: Product, quantity: number) { | ||||
|     this.quantity = quantity; | ||||
|     this.product = product; | ||||
|   } | ||||
|  | ||||
|   getBonusQuantity(): number { | ||||
|     if (!this.product.hasTrayPrice) { | ||||
|       return 0; | ||||
|     } | ||||
|     return Math.floor(this.quantity / quantityForTrayPrice); | ||||
|   } | ||||
|  | ||||
|   sum(): number { | ||||
|     return (this.quantity - this.getBonusQuantity()) * this.product.price; | ||||
|   } | ||||
| } | ||||
|  | ||||
| exportToHtml("loadCounter", (config: CounterConfig) => { | ||||
|   document.addEventListener("alpine:init", () => { | ||||
|     Alpine.data("counter", () => ({ | ||||
|       basket: config.sessionBasket, | ||||
|       basket: {} as Record<string, BasketItem>, | ||||
|       errors: [], | ||||
|       customerBalance: config.customerBalance, | ||||
|       codeField: undefined, | ||||
| @@ -26,17 +49,58 @@ exportToHtml("loadCounter", (config: CounterConfig) => { | ||||
|       init() { | ||||
|         this.codeField = this.$refs.codeField; | ||||
|         this.codeField.widget.focus(); | ||||
|         // It's quite tricky to manually apply attributes to the management part | ||||
|         // of a formset so we dynamically apply it here | ||||
|         this.$refs.basketManagementForm | ||||
|           .querySelector("#id_form-TOTAL_FORMS") | ||||
|           .setAttribute(":value", "getBasketSize()"); | ||||
|       }, | ||||
|  | ||||
|       getItemIdFromCode(code: string): string { | ||||
|         return Object.keys(config.products).find( | ||||
|           (key) => config.products[key].code === code, | ||||
|         ); | ||||
|       }, | ||||
|  | ||||
|       removeFromBasket(code: string) { | ||||
|         delete this.basket[this.getItemIdFromCode(code)]; | ||||
|       }, | ||||
|  | ||||
|       addToBasket(code: string, quantity: number): [boolean, string] { | ||||
|         const id = this.getItemIdFromCode(code); | ||||
|         const item: BasketItem = | ||||
|           this.basket[id] || new BasketItem(config.products[id], 0); | ||||
|  | ||||
|         const oldQty = item.quantity; | ||||
|         item.quantity += quantity; | ||||
|  | ||||
|         if (item.quantity <= 0) { | ||||
|           delete this.basket[id]; | ||||
|           return [true, ""]; | ||||
|         } | ||||
|  | ||||
|         if (item.sum() > this.customerBalance) { | ||||
|           item.quantity = oldQty; | ||||
|           return [false, gettext("Not enough money")]; | ||||
|         } | ||||
|  | ||||
|         this.basket[id] = item; | ||||
|         return [true, ""]; | ||||
|       }, | ||||
|  | ||||
|       getBasketSize() { | ||||
|         return Object.keys(this.basket).length; | ||||
|       }, | ||||
|  | ||||
|       sumBasket() { | ||||
|         if (!this.basket || Object.keys(this.basket).length === 0) { | ||||
|         if (this.getBasketSize() === 0) { | ||||
|           return 0; | ||||
|         } | ||||
|         const total = Object.values(this.basket).reduce( | ||||
|           (acc: number, cur: BasketItem) => acc + cur.qty * cur.price, | ||||
|           (acc: number, cur: BasketItem) => acc + cur.sum(), | ||||
|           0, | ||||
|         ) as number; | ||||
|         return total / 100; | ||||
|         return total; | ||||
|       }, | ||||
|  | ||||
|       onRefillingSuccess(event: CustomEvent) { | ||||
| @@ -50,33 +114,36 @@ exportToHtml("loadCounter", (config: CounterConfig) => { | ||||
|         this.codeField.widget.focus(); | ||||
|       }, | ||||
|  | ||||
|       async handleCode(event: SubmitEvent) { | ||||
|         const widget: TomSelect = this.codeField.widget; | ||||
|         const code = (widget.getValue() as string).toUpperCase(); | ||||
|         if (this.codeField.getOperationCodes().includes(code)) { | ||||
|           $(event.target).submit(); | ||||
|         } else { | ||||
|           await this.handleAction(event); | ||||
|         } | ||||
|         widget.clear(); | ||||
|         widget.focus(); | ||||
|       finish() { | ||||
|         this.$refs.basketForm.submit(); | ||||
|       }, | ||||
|  | ||||
|       async handleAction(event: SubmitEvent) { | ||||
|         const payload = $(event.target).serialize(); | ||||
|         const request = new Request(config.clickApiUrl, { | ||||
|           method: "POST", | ||||
|           body: payload, | ||||
|           headers: { | ||||
|             // biome-ignore lint/style/useNamingConvention: this goes into http headers | ||||
|             Accept: "application/json", | ||||
|             "X-CSRFToken": config.csrfToken, | ||||
|           }, | ||||
|       cancel() { | ||||
|         this.basket = new Object({}); | ||||
|         // We need to wait for the templated form to be removed before sending | ||||
|         this.$nextTick(() => { | ||||
|           this.finish(); | ||||
|         }); | ||||
|         const response = await fetch(request); | ||||
|         const json = await response.json(); | ||||
|         this.basket = json.basket; | ||||
|         this.errors = json.errors; | ||||
|       }, | ||||
|  | ||||
|       handleCode(event: SubmitEvent) { | ||||
|         const [quantity, code] = this.codeField.getSelectedProduct() as [ | ||||
|           number, | ||||
|           string, | ||||
|         ]; | ||||
|  | ||||
|         if (this.codeField.getOperationCodes().includes(code.toUpperCase())) { | ||||
|           if (code === "ANN") { | ||||
|             this.cancel(); | ||||
|           } | ||||
|           if (code === "FIN") { | ||||
|             this.finish(); | ||||
|           } | ||||
|         } else { | ||||
|           this.addToBasket(code, quantity); | ||||
|         } | ||||
|         this.codeField.widget.clear(); | ||||
|         this.codeField.widget.focus(); | ||||
|       }, | ||||
|     })); | ||||
|   }); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user