@meetelise/chat 1.43.5 → 1.43.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/src/WebComponent/FeeCalculator/components/addons/addon-table/addon-table.d.ts +4 -4
  2. package/dist/src/WebComponent/FeeCalculator/components/addons/addon-table/index.d.ts +0 -1
  3. package/dist/src/WebComponent/FeeCalculator/components/addons/addon-table/table-qty-selectors.d.ts +3 -2
  4. package/dist/src/WebComponent/FeeCalculator/components/charge-inputs/charge-inputs.d.ts +27 -0
  5. package/dist/src/WebComponent/FeeCalculator/components/fee-calculator-layout/fee-calculator-layout.d.ts +12 -12
  6. package/dist/src/WebComponent/FeeCalculator/components/fee-item/fee-item.d.ts +1 -1
  7. package/dist/src/WebComponent/FeeCalculator/components/fee-item/grouped-rentable-item.d.ts +0 -1
  8. package/dist/src/WebComponent/FeeCalculator/components/index.d.ts +1 -0
  9. package/dist/src/WebComponent/FeeCalculator/fee-calculator.d.ts +6 -5
  10. package/dist/src/WebComponent/FeeCalculator/model/desired-addon.d.ts +1 -1
  11. package/dist/src/WebComponent/FeeCalculator/model/fee-quote.d.ts +3 -4
  12. package/dist/src/WebComponent/FeeCalculator/model/index.d.ts +1 -4
  13. package/dist/src/WebComponent/FeeCalculator/model/item-combination.d.ts +1 -3
  14. package/dist/src/WebComponent/FeeCalculator/model/marketable-fee-new.d.ts +75 -0
  15. package/dist/src/WebComponent/FeeCalculator/model/marketable-fee.d.ts +79 -0
  16. package/dist/src/WebComponent/FeeCalculator/model/pricing-matrix.d.ts +1 -15
  17. package/dist/src/WebComponent/FeeCalculator/model/pricing-metadata.d.ts +1 -9
  18. package/dist/src/WebComponent/FeeCalculator/model/quote.d.ts +16 -2
  19. package/dist/src/disclaimers.d.ts +2 -1
  20. package/dist/src/services/fees/calculateQuote.d.ts +13 -3
  21. package/dist/src/services/fees/fetchBuildingFeesV2.d.ts +2 -2
  22. package/package.json +1 -1
  23. package/public/dist/index.js +571 -462
  24. package/src/WebComponent/FeeCalculator/components/addons/addon-table/addon-table.ts +28 -59
  25. package/src/WebComponent/FeeCalculator/components/addons/addon-table/index.ts +0 -1
  26. package/src/WebComponent/FeeCalculator/components/addons/addon-table/table-qty-selectors.ts +7 -10
  27. package/src/WebComponent/FeeCalculator/components/charge-inputs/charge-inputs.ts +351 -0
  28. package/src/WebComponent/FeeCalculator/components/fee-calculator-layout/fee-calculator-layout-styles.ts +26 -0
  29. package/src/WebComponent/FeeCalculator/components/fee-calculator-layout/fee-calculator-layout.ts +120 -86
  30. package/src/WebComponent/FeeCalculator/components/fee-card/fee-card.ts +19 -14
  31. package/src/WebComponent/FeeCalculator/components/fee-item/fee-item.ts +23 -12
  32. package/src/WebComponent/FeeCalculator/components/fee-item/grouped-rentable-item.ts +3 -13
  33. package/src/WebComponent/FeeCalculator/components/floor-plan-selector/floor-plan-selector.ts +1 -1
  34. package/src/WebComponent/FeeCalculator/components/index.ts +1 -0
  35. package/src/WebComponent/FeeCalculator/fee-calculator.ts +57 -64
  36. package/src/WebComponent/FeeCalculator/model/desired-addon.ts +1 -1
  37. package/src/WebComponent/FeeCalculator/model/fee-quote.ts +3 -4
  38. package/src/WebComponent/FeeCalculator/model/index.ts +1 -4
  39. package/src/WebComponent/FeeCalculator/model/item-combination.ts +2 -12
  40. package/src/WebComponent/FeeCalculator/model/marketable-fee-new.ts +81 -0
  41. package/src/WebComponent/FeeCalculator/model/marketable-fee.ts +124 -0
  42. package/src/WebComponent/FeeCalculator/model/pricing-matrix.ts +3 -39
  43. package/src/WebComponent/FeeCalculator/model/pricing-metadata.ts +2 -10
  44. package/src/WebComponent/FeeCalculator/model/quote.ts +16 -2
  45. package/src/WebComponent/Scheduler/tour-scheduler.ts +3 -0
  46. package/src/WebComponent/actions/email-us-window.ts +1 -0
  47. package/src/disclaimers.ts +17 -13
  48. package/src/services/fees/calculateQuote.ts +17 -8
  49. package/src/services/fees/downloadQuotePdf.ts +6 -8
  50. package/src/services/fees/fetchBuildingFeesV2.ts +10 -6
  51. package/src/utils.ts +1 -1
  52. package/dist/src/WebComponent/FeeCalculator/components/addons/addon-table/table-matrix-qty-selector-styles.d.ts +0 -1
  53. package/dist/src/WebComponent/FeeCalculator/components/addons/addon-table/table-matrix-qty-selector.d.ts +0 -23
  54. package/dist/src/WebComponent/FeeCalculator/model/building-fee-view.d.ts +0 -42
  55. package/dist/src/WebComponent/FeeCalculator/model/pricing-rule.d.ts +0 -10
  56. package/dist/src/WebComponent/FeeCalculator/model/pricing-type.d.ts +0 -10
  57. package/dist/src/services/fees/downloadQuotePdf.d.ts +0 -12
  58. package/src/WebComponent/FeeCalculator/components/addons/addon-table/table-matrix-qty-selector-styles.ts +0 -82
  59. package/src/WebComponent/FeeCalculator/components/addons/addon-table/table-matrix-qty-selector.ts +0 -203
  60. package/src/WebComponent/FeeCalculator/model/building-fee-view.ts +0 -105
  61. package/src/WebComponent/FeeCalculator/model/pricing-rule.ts +0 -11
  62. package/src/WebComponent/FeeCalculator/model/pricing-type.ts +0 -11
@@ -20,22 +20,25 @@ const formDisclaimer = ({
20
20
  emailInput,
21
21
  orgLegalName,
22
22
  orgSlug,
23
+ clickingButtonText = "send",
23
24
  }: {
24
25
  buildingName: string;
25
26
  phoneNumberInput?: string;
26
27
  emailInput?: string;
27
28
  orgLegalName: string;
28
29
  orgSlug: string;
30
+ clickingButtonText?: string;
29
31
  }): TemplateResult => {
30
32
  if (phoneNumberInput && orgSlug === "greystar") {
31
33
  return html`<div style=${styleMap(disclaimerStyles.container)}>
32
- By providing your phone number and clicking send, you consent to receive
33
- recurring marketing calls and voice and text messages from Greystar or on
34
- its behalf at this number using artificial voice and/or an autodialer.
35
- Messages may be AI or human-generated. Your phone number and
36
- communications are shared with and recorded and used by EliseAI. You are
37
- not required to provide your phone number or consent to these terms to
38
- lease at this property. Msg & Data rates may apply. You consent to
34
+ By providing your phone number and clicking ${clickingButtonText}, you
35
+ consent to receive recurring marketing calls and voice and text messages
36
+ from Greystar or on its behalf at this number using artificial voice
37
+ and/or an autodialer. Messages may be AI or human-generated. Your phone
38
+ number and communications are shared with and recorded and used by
39
+ EliseAI. You are not required to provide your phone number or consent to
40
+ these terms to lease at this property. Msg & Data rates may apply. You
41
+ consent to
39
42
  <a
40
43
  style=${styleMap(disclaimerStyles.link)}
41
44
  href="https://www.greystar.com/privacy"
@@ -55,11 +58,11 @@ const formDisclaimer = ({
55
58
  }
56
59
  if (phoneNumberInput) {
57
60
  return html` <div style=${styleMap(disclaimerStyles.container)}>
58
- By providing your number and clicking send, you consent to receive
59
- recurring marketing calls and voice and text messages from or on behalf of
60
- ${orgLegalName} at this number using artificial voice or an autodialer
61
- system. Messages may be AI or human generated. This consent is not
62
- required to lease at this property. Msg & Data rates may apply. You
61
+ By providing your number and clicking ${clickingButtonText}, you consent
62
+ to receive recurring marketing calls and voice and text messages from or
63
+ on behalf of ${orgLegalName} at this number using artificial voice or an
64
+ autodialer system. Messages may be AI or human generated. This consent is
65
+ not required to lease at this property. Msg & Data rates may apply. You
63
66
  consent to this
64
67
  <a
65
68
  style=${styleMap(disclaimerStyles.link)}
@@ -73,7 +76,8 @@ const formDisclaimer = ({
73
76
  }
74
77
  if (emailInput) {
75
78
  return html` <div style=${styleMap(disclaimerStyles.container)}>
76
- By entering your email and clicking submit, you consent to this
79
+ By entering your email and clicking ${clickingButtonText}, you consent to
80
+ this
77
81
  <a
78
82
  style=${styleMap(disclaimerStyles.link)}
79
83
  href="http://bit.ly/me_privacy_policy"
@@ -2,17 +2,25 @@ import axios, { AxiosError } from "axios";
2
2
 
3
3
  import { LogType, sendLoggingEvent } from "../../analytics";
4
4
  import { camelize, snakify } from "../../utils";
5
- import {
6
- DesiredAddon,
7
- DesiredRentableItem,
8
- } from "../../WebComponent/FeeCalculator/model";
5
+ import { DesiredRentableItem } from "../../WebComponent/FeeCalculator/model";
6
+
7
+ // ChargeInputs interface matching backend structure
8
+ export interface ChargeInputs {
9
+ base_rent: number;
10
+ num_pets: number;
11
+ num_vehicles: number;
12
+ num_applicants: number;
13
+ num_cats?: number;
14
+ num_dogs?: number;
15
+ num_other_pets?: number;
16
+ }
9
17
  import { Quote } from "../../WebComponent/FeeCalculator/model/quote";
10
18
  import { BASE_DOMAIN } from "../../globals";
11
19
 
12
20
  type CalculateQuoteRequest = {
13
21
  buildingSlug: string;
14
22
  unitId: number;
15
- addons: DesiredAddon[];
23
+ chargeInputs: ChargeInputs;
16
24
  rentableItems: DesiredRentableItem[];
17
25
  leaseTerm: number;
18
26
  moveInDate: string;
@@ -20,6 +28,7 @@ type CalculateQuoteRequest = {
20
28
 
21
29
  export type CalculateQuoteResponse = {
22
30
  quote: Quote;
31
+ pdfUrl?: string;
23
32
  };
24
33
 
25
34
  const BASE_URL = BASE_DOMAIN;
@@ -28,7 +37,7 @@ export const calculateQuote = async (
28
37
  {
29
38
  buildingSlug,
30
39
  unitId,
31
- addons,
40
+ chargeInputs,
32
41
  rentableItems,
33
42
  leaseTerm,
34
43
  moveInDate,
@@ -37,10 +46,10 @@ export const calculateQuote = async (
37
46
  ): Promise<CalculateQuoteResponse> => {
38
47
  try {
39
48
  const feesResponse = await axios.post(
40
- `${BASE_URL}/platformApi/webchat/${buildingSlug}/fees/v2/calculate`,
49
+ `${BASE_URL}/platformApi/webchat/${buildingSlug}/quote/calculate`,
41
50
  snakify({
42
51
  unitId,
43
- addons,
52
+ chargeInputs,
44
53
  rentableItems,
45
54
  leaseTerm,
46
55
  moveInDate,
@@ -2,17 +2,15 @@ import axios from "axios";
2
2
 
3
3
  import { LogType, sendLoggingEvent } from "../../analytics";
4
4
  import { snakify } from "../../utils";
5
- import {
6
- DesiredAddon,
7
- DesiredRentableItem,
8
- } from "../../WebComponent/FeeCalculator/model";
5
+ import { DesiredRentableItem } from "../../WebComponent/FeeCalculator/model";
6
+ import { ChargeInputs } from "./calculateQuote";
9
7
  import { BASE_DOMAIN } from "../../globals";
10
8
 
11
9
  type DownloadQuotePdfRequest = {
12
10
  buildingSlug: string;
13
11
  unitId: number;
14
12
  unitNumber: string;
15
- addons: DesiredAddon[];
13
+ chargeInputs: ChargeInputs;
16
14
  rentableItems: DesiredRentableItem[];
17
15
  leaseTerm: number;
18
16
  moveInDate: string;
@@ -24,17 +22,17 @@ export const downloadQuotePdf = async ({
24
22
  buildingSlug,
25
23
  unitId,
26
24
  unitNumber,
27
- addons,
25
+ chargeInputs,
28
26
  rentableItems,
29
27
  leaseTerm,
30
28
  moveInDate,
31
29
  }: DownloadQuotePdfRequest): Promise<void> => {
32
30
  try {
33
31
  const response = await axios.post(
34
- `${BASE_URL}/platformApi/webchat/${buildingSlug}/fees/calculate/download`,
32
+ `${BASE_URL}/platformApi/webchat/${buildingSlug}/quote/download-pdf`,
35
33
  snakify({
36
34
  unitId,
37
- addons,
35
+ chargeInputs,
38
36
  rentableItems,
39
37
  leaseTerm,
40
38
  moveInDate,
@@ -1,16 +1,19 @@
1
1
  import axios from "axios";
2
2
  import { LogType, sendLoggingEvent } from "../../analytics";
3
- import { BASE_DOMAIN } from "../../globals";
4
3
  import { camelize } from "../../utils";
5
4
  import { IncentiveV2 } from "../../types/incentive-v2";
6
5
  import { RentableItemSummary } from "../../WebComponent/FeeCalculator/model/rentable-item-summary";
7
- import { BuildingFeeView } from "../../WebComponent/FeeCalculator/model/building-fee-view";
6
+ import {
7
+ MarketableFee,
8
+ MarketableFeeModel,
9
+ } from "../../WebComponent/FeeCalculator/model/marketable-fee";
8
10
  import QueryParamBuilder from "../../utils/queryParamBuilder";
11
+ import { BASE_DOMAIN } from "../../globals";
9
12
 
10
13
  const BASE_URL = BASE_DOMAIN;
11
14
 
12
15
  type BuildingFeeResponse = {
13
- fees: BuildingFeeView[];
16
+ fees: MarketableFee[];
14
17
  rentableItems: RentableItemSummary[];
15
18
  buildingIncentives: IncentiveV2[];
16
19
  };
@@ -25,12 +28,12 @@ const fetchBuildingFeesV2 = async (
25
28
  layoutIds?.map((id) => id.toString())
26
29
  );
27
30
  const feesResponse = await axios.get(
28
- `${BASE_URL}/platformApi/webchat/${buildingSlug}/fees?${params.build()}`
31
+ `${BASE_URL}/platformApi/webchat/${buildingSlug}/marketable-fees?${params.build()}`
29
32
  );
30
33
  if (feesResponse.data) {
31
34
  const data = camelize<BuildingFeeResponse>(feesResponse.data);
32
35
  return {
33
- fees: data.fees.map(toBuildingFeeClass),
36
+ fees: data.fees.map(toMarketableFeeClass),
34
37
  rentableItems: data.rentableItems.map(toRentableItemSummary),
35
38
  buildingIncentives: data.buildingIncentives,
36
39
  };
@@ -51,7 +54,8 @@ const fetchBuildingFeesV2 = async (
51
54
  };
52
55
  };
53
56
 
54
- const toBuildingFeeClass = (fee: BuildingFeeView) => new BuildingFeeView(fee);
57
+ const toMarketableFeeClass = (fee: MarketableFee) =>
58
+ new MarketableFeeModel(fee);
55
59
 
56
60
  const toRentableItemSummary = (item: RentableItemSummary) =>
57
61
  new RentableItemSummary(item);
package/src/utils.ts CHANGED
@@ -117,7 +117,7 @@ export const formatCurrency = (
117
117
  return Intl.NumberFormat("en-US", {
118
118
  style: "currency",
119
119
  currency: "USD",
120
- minimumFractionDigits: 0,
120
+ minimumFractionDigits: maximumFractionDigits,
121
121
  maximumFractionDigits,
122
122
  }).format(amount ?? 0);
123
123
  };
@@ -1 +0,0 @@
1
- export declare const tableMatrixStyles: import("lit").CSSResult;
@@ -1,23 +0,0 @@
1
- import { LitElement, TemplateResult } from "lit";
2
- import { BuildingFeeView, DesiredAddon } from "../../../model";
3
- export declare class TableMatrixQtySelector extends LitElement {
4
- static styles: import("lit").CSSResult;
5
- set feeItem(value: BuildingFeeView | null);
6
- get feeItem(): BuildingFeeView | null;
7
- private _feeItem;
8
- onQuantityChange: ((addon: DesiredAddon) => void) | null;
9
- disabled: boolean;
10
- private quantities;
11
- private _availableTypes;
12
- private get matrixRules();
13
- private getAvailableTypes;
14
- private get itemTypes();
15
- private getMaxQuantityForType;
16
- private isValidCombination;
17
- private handleIncrement;
18
- private handleDecrement;
19
- private notifyQuantityChange;
20
- private formatItemType;
21
- private renderItemType;
22
- render(): TemplateResult;
23
- }
@@ -1,42 +0,0 @@
1
- import { PricingMatrix } from "./pricing-matrix";
2
- export declare enum BuildingFeeFrequency {
3
- OneTime = "One Time",
4
- Monthly = "Monthly",
5
- Quarterly = "Quarterly",
6
- Annually = "Annually",
7
- PerOccurrence = "Per Occurrence"
8
- }
9
- export declare class BuildingFeeView {
10
- id?: number;
11
- buildingId: number;
12
- feeName: string;
13
- description?: string;
14
- category: string;
15
- unitIds?: number[];
16
- layoutIds?: number[];
17
- startDate?: Date;
18
- endDate?: Date;
19
- source?: string;
20
- externalKey?: string;
21
- syncActive: boolean;
22
- hiddenReason?: string;
23
- refundable: boolean;
24
- bundleKey?: string;
25
- publicFacing: boolean;
26
- chargeCode?: string;
27
- prorated?: boolean;
28
- pricingCategory: "mandatory" | "situational";
29
- pricingType: "fixed" | "per-item" | "usage-based" | "percent-rent" | "custom";
30
- frequency: BuildingFeeFrequency;
31
- nlpPricing?: string;
32
- isAddon: boolean;
33
- amount?: number;
34
- percentRent?: number;
35
- unitLabel?: string;
36
- minAmount?: number;
37
- maxAmount?: number;
38
- customMatrixData?: PricingMatrix;
39
- constructor(data?: Partial<BuildingFeeView>);
40
- get isOptional(): boolean;
41
- get hasMatrix(): boolean;
42
- }
@@ -1,10 +0,0 @@
1
- import { ItemCombination } from "./item-combination";
2
- export interface PricingRule {
3
- combination: ItemCombination;
4
- price?: number;
5
- percentRent?: number;
6
- percentRentMin?: number;
7
- percentRentMax?: number;
8
- minimumPrice?: number;
9
- maximumPrice?: number;
10
- }
@@ -1,10 +0,0 @@
1
- declare enum PricingType {
2
- FIXED = "FIXED",
3
- USAGE_BASED = "USAGE_BASED",
4
- PERCENT_RENT = "PERCENT_RENT",
5
- PER_ITEM = "PER_ITEM",
6
- CUSTOM = "CUSTOM",
7
- FIXED_MAX_PERCENT_RENT = "FIXED_MAX_PERCENT_RENT",
8
- FIXED_MIN_PERCENT_RENT = "FIXED_MIN_PERCENT_RENT"
9
- }
10
- export default PricingType;
@@ -1,12 +0,0 @@
1
- import { DesiredAddon, DesiredRentableItem } from "../../WebComponent/FeeCalculator/model";
2
- declare type DownloadQuotePdfRequest = {
3
- buildingSlug: string;
4
- unitId: number;
5
- unitNumber: string;
6
- addons: DesiredAddon[];
7
- rentableItems: DesiredRentableItem[];
8
- leaseTerm: number;
9
- moveInDate: string;
10
- };
11
- export declare const downloadQuotePdf: ({ buildingSlug, unitId, unitNumber, addons, rentableItems, leaseTerm, moveInDate, }: DownloadQuotePdfRequest) => Promise<void>;
12
- export default downloadQuotePdf;
@@ -1,82 +0,0 @@
1
- import { css } from "lit";
2
-
3
- export const tableMatrixStyles = css`
4
- :host {
5
- display: block;
6
- }
7
-
8
- .matrix-container {
9
- display: flex;
10
- flex-direction: column;
11
- gap: 8px;
12
- }
13
-
14
- .item-type-row {
15
- display: flex;
16
- align-items: center;
17
- justify-content: space-between;
18
- gap: 8px;
19
- }
20
-
21
- .item-label {
22
- flex: 1;
23
- font-size: 0.875rem;
24
- color: #4b5563;
25
- text-align: left;
26
- }
27
-
28
- .quantity-control {
29
- display: inline-flex;
30
- justify-content: center;
31
- align-items: center;
32
- border: 1px solid #dee2e6;
33
- border-radius: 4px;
34
- background-color: #fff;
35
- }
36
-
37
- .quantity-control.unavailable {
38
- background-color: #f9f9f9;
39
- border-color: #e0e0e0;
40
- cursor: not-allowed;
41
- }
42
-
43
- .operator-sign {
44
- display: inline-block;
45
- line-height: 1;
46
- position: relative;
47
- transform: translateY(1px);
48
- width: 0.8rem;
49
- text-align: center;
50
- }
51
-
52
- .quantity-button {
53
- font-size: 1.1rem;
54
- padding: 0.3rem 0.4rem;
55
- background: none;
56
- border: none;
57
- color: #347ff7;
58
- cursor: pointer;
59
- }
60
-
61
- .quantity-button:hover {
62
- background-color: #f3f4f6;
63
- }
64
-
65
- .quantity-button:disabled {
66
- opacity: 0.5;
67
- cursor: not-allowed;
68
- }
69
-
70
- .quantity-value {
71
- padding: 0.25rem;
72
- min-width: 1.5rem;
73
- text-align: center;
74
- font-size: 0.875rem;
75
- }
76
-
77
- .unavailable-text {
78
- color: #9ca3af;
79
- font-style: italic;
80
- cursor: not-allowed;
81
- }
82
- `;
@@ -1,203 +0,0 @@
1
- import { LitElement, html, TemplateResult } from "lit";
2
- import { customElement, property, state } from "lit/decorators.js";
3
- import { BuildingFeeView, DesiredAddon, PricingRule } from "../../../model";
4
-
5
- import { tableMatrixStyles } from "./table-matrix-qty-selector-styles";
6
-
7
- @customElement("table-matrix-qty-selector")
8
- export class TableMatrixQtySelector extends LitElement {
9
- static styles = tableMatrixStyles;
10
-
11
- @property({ type: Object })
12
- set feeItem(value: BuildingFeeView | null) {
13
- const oldValue = this._feeItem;
14
- const oldId = oldValue?.id ?? null;
15
- const newId = value?.id ?? null;
16
-
17
- this._feeItem = value;
18
- if (newId !== oldId) {
19
- this.quantities = {};
20
- this._availableTypes = this.getAvailableTypes();
21
- }
22
- this.requestUpdate("feeItem", oldValue);
23
- }
24
- get feeItem(): BuildingFeeView | null {
25
- return this._feeItem;
26
- }
27
- private _feeItem: BuildingFeeView | null = null;
28
-
29
- @property()
30
- onQuantityChange: ((addon: DesiredAddon) => void) | null = null;
31
-
32
- @property({ type: Boolean, reflect: true })
33
- disabled = false;
34
-
35
- @state()
36
- private quantities: Record<string, number> = {};
37
-
38
- // Cache available types to prevent recalculation on each render
39
- private _availableTypes: string[] = [];
40
-
41
- private get matrixRules(): PricingRule[] {
42
- return this.feeItem?.customMatrixData?.toRuleArray() ?? [];
43
- }
44
-
45
- private getAvailableTypes(): string[] {
46
- const availableTypes = new Set<string>();
47
- this.matrixRules.forEach((rule) => {
48
- rule.combination.quantities.forEach((q) =>
49
- availableTypes.add(q.itemType)
50
- );
51
- });
52
- return Array.from(availableTypes).sort();
53
- }
54
-
55
- private get itemTypes(): string[] {
56
- return this._availableTypes;
57
- }
58
-
59
- private getMaxQuantityForType(itemType: string): number {
60
- let max = 0;
61
- this.matrixRules.forEach((rule) => {
62
- const itemQty = rule.combination.quantities.find(
63
- (q) => q.itemType === itemType
64
- );
65
- if (itemQty) {
66
- max = Math.max(max, itemQty.quantity);
67
- }
68
- });
69
- return max;
70
- }
71
-
72
- private isValidCombination(type: string, newQuantity: number): boolean {
73
- const proposedQuantities = { ...this.quantities, [type]: newQuantity };
74
-
75
- const relevantRules = this.matrixRules.filter((rule) =>
76
- rule.combination.quantities.some((q) => q.itemType === type)
77
- );
78
-
79
- return relevantRules.some((rule) => {
80
- const ruleQuantities = new Map(
81
- rule.combination.quantities.map((q) => [q.itemType, q.quantity])
82
- );
83
-
84
- return Array.from(ruleQuantities.entries()).every(
85
- ([itemType, quantity]) => {
86
- const proposedQty = proposedQuantities[itemType] ?? 0;
87
- return proposedQty === quantity;
88
- }
89
- );
90
- });
91
- }
92
-
93
- private handleIncrement(type: string): void {
94
- if (this.disabled) return;
95
-
96
- const currentQty = this.quantities[type] ?? 0;
97
- const newQty = currentQty + 1;
98
-
99
- if (newQty > this.getMaxQuantityForType(type)) return;
100
- if (!this.isValidCombination(type, newQty)) return;
101
-
102
- const newQuantities = { ...this.quantities };
103
- newQuantities[type] = newQty;
104
- this.quantities = newQuantities;
105
-
106
- this.notifyQuantityChange();
107
- }
108
-
109
- private handleDecrement(type: string): void {
110
- if (this.disabled) return;
111
-
112
- const currentQty = this.quantities[type] ?? 0;
113
- if (currentQty <= 0) return;
114
-
115
- const newQty = currentQty - 1;
116
- if (!this.isValidCombination(type, newQty)) return;
117
-
118
- const newQuantities = { ...this.quantities };
119
- newQuantities[type] = newQty;
120
- this.quantities = newQuantities;
121
-
122
- this.notifyQuantityChange();
123
- }
124
-
125
- private notifyQuantityChange(): void {
126
- if (!this.onQuantityChange || !this.feeItem) return;
127
-
128
- // Include all available types, using 0 as default quantity if not set
129
- // e.g. { "dog": 1, "cat": 2 }
130
- const quantities = this.itemTypes.map((type) => ({
131
- itemType: type,
132
- quantity: this.quantities[type] ?? 0,
133
- }));
134
-
135
- this.onQuantityChange({
136
- id: this.feeItem.id ?? 0,
137
- quantities,
138
- });
139
- }
140
-
141
- private formatItemType(type: string): string {
142
- return type
143
- .split(/[_-]/)
144
- .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
145
- .join(" ");
146
- }
147
-
148
- private renderItemType(type: string, isSingleItem: boolean): TemplateResult {
149
- const quantity = this.quantities[type] ?? 0;
150
- const maxQty = this.getMaxQuantityForType(type);
151
- const canIncrement =
152
- this.isValidCombination(type, quantity + 1) && quantity < maxQty;
153
- const formattedType = this.formatItemType(type);
154
-
155
- const showTypeLabel = !isSingleItem;
156
-
157
- return html`
158
- <div class="item-type-row">
159
- ${showTypeLabel
160
- ? html`<div class="item-label">${formattedType}:</div>`
161
- : html`<div></div>`}
162
- <div class="quantity-control ${this.disabled ? "unavailable" : ""}">
163
- <button
164
- class="quantity-button"
165
- @click=${() => this.handleDecrement(type)}
166
- ?disabled=${this.disabled || quantity <= 0}
167
- aria-label="Decrease ${type} quantity"
168
- >
169
- <span class="operator-sign">&minus;</span>
170
- </button>
171
- <span
172
- class="quantity-value ${this.disabled ? "unavailable-text" : ""}"
173
- >
174
- ${quantity}
175
- </span>
176
- <button
177
- class="quantity-button"
178
- @click=${() => this.handleIncrement(type)}
179
- ?disabled=${this.disabled || !canIncrement}
180
- aria-label="Increase ${type} quantity"
181
- >
182
- <span class="operator-sign">+</span>
183
- </button>
184
- </div>
185
- </div>
186
- `;
187
- }
188
-
189
- render(): TemplateResult {
190
- if (!this.feeItem || !this.matrixRules.length) return html``;
191
-
192
- const types = this.itemTypes;
193
- if (!types.length) return html``;
194
-
195
- const isSingleItem = types.length === 1;
196
-
197
- return html`
198
- <div class="matrix-container">
199
- ${types.map((type) => this.renderItemType(type, isSingleItem))}
200
- </div>
201
- `;
202
- }
203
- }