@fleetbase/storefront-engine 0.3.24 → 0.3.25

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 (110) hide show
  1. package/addon/adapters/catalog.js +1 -0
  2. package/addon/adapters/food-truck.js +1 -0
  3. package/addon/components/customer-panel/orders.hbs +7 -1
  4. package/addon/components/modals/assign-food-truck-catalogs.hbs +14 -0
  5. package/addon/components/modals/create-catalog.hbs +96 -0
  6. package/addon/components/modals/create-first-store.hbs +6 -1
  7. package/addon/components/modals/create-food-truck.hbs +69 -0
  8. package/addon/components/modals/manage-addons.js +1 -15
  9. package/addon/components/order-panel.hbs +2 -2
  10. package/addon/components/widget/customers.hbs +5 -5
  11. package/addon/components/widget/orders.hbs +7 -4
  12. package/addon/components/widget/orders.js +11 -1
  13. package/addon/controllers/catalogs/index.js +121 -0
  14. package/addon/controllers/food-trucks/index.js +100 -0
  15. package/addon/models/catalog-category.js +6 -0
  16. package/addon/models/catalog-hour.js +72 -0
  17. package/addon/models/catalog.js +45 -0
  18. package/addon/models/food-truck.js +47 -0
  19. package/addon/models/product.js +1 -0
  20. package/addon/routes/catalogs/index.js +22 -0
  21. package/addon/routes/catalogs.js +3 -0
  22. package/addon/routes/food-trucks/index.js +22 -0
  23. package/addon/routes/food-trucks.js +3 -0
  24. package/addon/routes.js +6 -0
  25. package/addon/serializers/catalog-category.js +15 -0
  26. package/addon/serializers/catalog.js +16 -0
  27. package/addon/serializers/food-truck.js +18 -0
  28. package/addon/serializers/product-variant.js +0 -1
  29. package/addon/styles/storefront-engine.css +15 -5
  30. package/addon/templates/application.hbs +14 -0
  31. package/addon/templates/catalogs/index.hbs +46 -0
  32. package/addon/templates/catalogs.hbs +1 -0
  33. package/addon/templates/customers/index.hbs +1 -1
  34. package/addon/templates/food-trucks/index.hbs +52 -0
  35. package/addon/templates/food-trucks.hbs +1 -0
  36. package/addon/templates/networks/index.hbs +14 -2
  37. package/addon/templates/products/index/category/new.hbs +10 -3
  38. package/addon/templates/products/index/index.hbs +1 -2
  39. package/addon/templates/products/index.hbs +16 -2
  40. package/app/adapters/catalog.js +1 -0
  41. package/app/adapters/food-truck.js +1 -0
  42. package/app/components/modals/assign-food-truck-catalogs.js +1 -0
  43. package/app/components/modals/create-catalog.js +1 -0
  44. package/app/components/modals/create-food-truck.js +1 -0
  45. package/app/controllers/catalogs/index.js +1 -0
  46. package/app/controllers/food-trucks/index.js +1 -0
  47. package/app/models/catalog-category.js +1 -0
  48. package/app/models/catalog-hour.js +1 -0
  49. package/app/models/catalog.js +1 -0
  50. package/app/models/food-truck.js +1 -0
  51. package/app/routes/catalogs/index.js +1 -0
  52. package/app/routes/catalogs.js +1 -0
  53. package/app/routes/food-trucks/index.js +1 -0
  54. package/app/routes/food-trucks.js +1 -0
  55. package/app/serializers/catalog-category.js +1 -0
  56. package/app/serializers/catalog.js +1 -0
  57. package/app/serializers/food-truck.js +1 -0
  58. package/app/templates/catalogs/index.js +1 -0
  59. package/app/templates/catalogs.js +1 -0
  60. package/app/templates/food-trucks/index.js +1 -0
  61. package/app/templates/food-trucks.js +1 -0
  62. package/composer.json +1 -1
  63. package/extension.json +1 -1
  64. package/package.json +1 -1
  65. package/server/migrations/2025_01_30_042853_create_catalogs_table.php +41 -0
  66. package/server/migrations/2025_01_30_044728_create_catalog_category_products_table.php +35 -0
  67. package/server/migrations/2025_01_30_050611_create_food_trucks_table.php +72 -0
  68. package/server/migrations/2025_01_30_052157_create_catalog_subjects_table.php +54 -0
  69. package/server/migrations/2025_01_30_052402_create_catalog_hours_table.php +39 -0
  70. package/server/src/Auth/Schemas/Storefront.php +16 -0
  71. package/server/src/Console/Commands/PurgeExpiredCarts.php +13 -3
  72. package/server/src/Http/Controllers/CatalogCategoryController.php +13 -0
  73. package/server/src/Http/Controllers/CatalogController.php +13 -0
  74. package/server/src/Http/Controllers/CatalogHourController.php +13 -0
  75. package/server/src/Http/Controllers/FoodTruckController.php +13 -0
  76. package/server/src/Http/Controllers/v1/CatalogController.php +38 -0
  77. package/server/src/Http/Controllers/v1/CategoryController.php +1 -1
  78. package/server/src/Http/Controllers/v1/FoodTruckController.php +39 -0
  79. package/server/src/Http/Controllers/v1/ProductController.php +1 -1
  80. package/server/src/Http/Controllers/v1/ReviewController.php +8 -6
  81. package/server/src/Http/Filter/FoodTruckFilter.php +37 -0
  82. package/server/src/Http/Resources/Catalog.php +34 -0
  83. package/server/src/Http/Resources/CatalogCategory.php +38 -0
  84. package/server/src/Http/Resources/CatalogProduct.php +55 -0
  85. package/server/src/Http/Resources/FoodTruck.php +42 -0
  86. package/server/src/Http/Resources/Product.php +1 -0
  87. package/server/src/Models/AddonCategory.php +12 -0
  88. package/server/src/Models/Cart.php +6 -0
  89. package/server/src/Models/Catalog.php +213 -0
  90. package/server/src/Models/CatalogCategory.php +118 -0
  91. package/server/src/Models/CatalogHour.php +46 -0
  92. package/server/src/Models/CatalogProduct.php +25 -0
  93. package/server/src/Models/CatalogSubject.php +70 -0
  94. package/server/src/Models/FoodTruck.php +182 -0
  95. package/server/src/Models/Product.php +16 -1
  96. package/server/src/Notifications/StorefrontOrderCanceled.php +80 -121
  97. package/server/src/Notifications/StorefrontOrderCompleted.php +86 -131
  98. package/server/src/Notifications/StorefrontOrderCreated.php +29 -15
  99. package/server/src/Notifications/StorefrontOrderDriverAssigned.php +96 -130
  100. package/server/src/Notifications/StorefrontOrderEnroute.php +89 -129
  101. package/server/src/Notifications/StorefrontOrderNearby.php +103 -132
  102. package/server/src/Notifications/StorefrontOrderPreparing.php +84 -134
  103. package/server/src/Notifications/StorefrontOrderReadyForPickup.php +89 -134
  104. package/server/src/Observers/CatalogObserver.php +40 -0
  105. package/server/src/Observers/FoodTruckObserver.php +24 -0
  106. package/server/src/Observers/ProductObserver.php +3 -27
  107. package/server/src/Providers/StorefrontServiceProvider.php +5 -3
  108. package/server/src/Support/PushNotification.php +127 -0
  109. package/server/src/routes.php +10 -0
  110. package/translations/en-us.yaml +5 -0
@@ -0,0 +1 @@
1
+ export { default } from './storefront';
@@ -0,0 +1 @@
1
+ export { default } from './storefront';
@@ -42,7 +42,13 @@
42
42
  />
43
43
  {{/if}}
44
44
  {{#if order.isPreparing}}
45
- <Button @size="xs" @type="success" @icon="bell-concierge" @text={{t "storefront.component.widget.orders.mark-as-ready"}} @onClick={{fn this.markAsReady order}} />
45
+ <Button
46
+ @size="xs"
47
+ @type="success"
48
+ @icon="bell-concierge"
49
+ @text={{t "storefront.component.widget.orders.mark-as-ready"}}
50
+ @onClick={{fn this.markAsReady order}}
51
+ />
46
52
  {{/if}}
47
53
  {{#if order.isPickupReady}}
48
54
  <Button
@@ -0,0 +1,14 @@
1
+ <Modal::Default @modalIsOpened={{@modalIsOpened}} @options={{@options}} @confirm={{@onConfirm}} @decline={{@onDecline}}>
2
+ <div class="modal-body-container">
3
+ <InputGroup>
4
+ <ComboBox
5
+ @options={{@options.allCatalogs}}
6
+ @selected={{@options.foodTruck.catalogs}}
7
+ @optionLabel="name"
8
+ @comparator="name"
9
+ @selectionBoxLabel="Selected Catalogs"
10
+ @onChange={{@options.updateCatalogSelections}}
11
+ />
12
+ </InputGroup>
13
+ </div>
14
+ </Modal::Default>
@@ -0,0 +1,96 @@
1
+ <Modal::Default @modalIsOpened={{@modalIsOpened}} @options={{@options}} @confirm={{@onConfirm}} @decline={{@onDecline}}>
2
+ <div class="modal-body-container">
3
+ <InputGroup @name="Catalog Name" @value={{@options.catalog.name}} @disabled={{cannot "storefront create catalog"}} />
4
+ <InputGroup @name="Catalog Description" @value={{@options.catalog.description}} @disabled={{cannot "storefront create catalog"}} />
5
+ <InputGroup @name="Catalog Status">
6
+ <Select
7
+ class="w-full"
8
+ @value={{@options.catalog.status}}
9
+ @placeholder="Select catalog status"
10
+ @options={{@options.statusOptions}}
11
+ @onSelect={{fn (mut @options.catalog.status)}}
12
+ @humanize={{true}}
13
+ />
14
+ </InputGroup>
15
+ <InputGroup>
16
+ <div class="rounded-lg flex flex-col px-3 py-2 bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-700">
17
+ <div class="mb-2 flex justify-between">
18
+ <div class="text-sm font-semibold dark:text-gray-100">Product Catalog</div>
19
+ <div>
20
+ <Button @icon="plus" @text="Add Catalog Category" @onClick={{@options.createCategory}} />
21
+ </div>
22
+ </div>
23
+ <div class="space-y-2">
24
+ {{#each @options.catalog.categories as |category|}}
25
+ <div>
26
+ <div
27
+ class="{{if category.deleting 'opacity-50'}}
28
+ {{if category.expanded 'rounded-t-lg' 'rounded-lg'}}
29
+ flex flex-row items-center justify-between px-2 py-1 bg-gray-200 dark:bg-gray-700 border border-gray-100 dark:border-gray-900 shadow-sm"
30
+ >
31
+ <div>
32
+ <div class="text-sm dark:text-gray-100">{{category.name}}</div>
33
+ </div>
34
+ <div class="flex flex-row items-center space-x-1">
35
+ <Button
36
+ @icon={{if category.expanded "compress" "expand"}}
37
+ @onClick={{fn @options.toggleCategory category}}
38
+ @helpText="Collapse or expand category products"
39
+ @size="xs"
40
+ />
41
+ <Button @icon="tags" @onClick={{fn @options.addProducts category}} @helpText="Manage category products" @size="xs" />
42
+ <Button @icon="pencil" @onClick={{fn @options.editCategory category}} @helpText="Edit category name" @size="xs" />
43
+ <Button @type="danger" @icon="trash" @onClick={{fn @options.deleteCategory category}} @helpText="Delete category" @size="xs" />
44
+ </div>
45
+ </div>
46
+ {{#if category.expanded}}
47
+ <div class="rounded-b-lg flex flex-col px-2 py-1 bg-gray-300 dark:bg-gray-900 border border-gray-100 dark:border-gray-900 shadow-sm">
48
+ {{#if category.adding_products}}
49
+ <div>
50
+ <ComboBox
51
+ @options={{@options.allProducts}}
52
+ @selected={{category.products}}
53
+ @optionLabel="name"
54
+ @comparator="name"
55
+ @selectionBoxLabel="Selected"
56
+ @onChange={{fn @options.confirmSelectedProducts category}}
57
+ />
58
+ <div class="mt-2">
59
+ <Button @icon="check" @type="primary" @text="Done" @onClick={{fn @options.finishAddingProducts category}} />
60
+ </div>
61
+ </div>
62
+ {{else}}
63
+ <div class="flex flex-row mb-2">
64
+ <Button @icon="edit" @text="Edit Products" @onClick={{fn @options.addProducts category}} @size="xs" />
65
+ </div>
66
+ <div class="grid grid-cols-4 gap-2">
67
+ {{#each category.products as |product|}}
68
+ <div class="rounded-lg bg-gray-100 dark:bg-gray-800 border dark:border-gray-700 border-gray-200">
69
+ <Image src={{product.primary_image_url}} class="rounded-t-lg w-full h-32" />
70
+ <div class="flex flex-col p-2">
71
+ <div class="text-sm truncate font-semibold dark:text-gray-100">{{product.name}}</div>
72
+ <div class="text-sm truncate dark:text-gray-200">{{n-a product.description}}</div>
73
+ <div class="text-sm dark:text-gray-200">{{format-currency product.price product.currency}}</div>
74
+ <div class="mt-2">
75
+ <Button @type="danger" @icon="trash" @text="Remove" @onClick={{fn @options.removeProduct category product}} @size="xs" />
76
+ </div>
77
+ </div>
78
+ </div>
79
+ {{/each}}
80
+ </div>
81
+ {{/if}}
82
+ </div>
83
+ {{/if}}
84
+ </div>
85
+ {{/each}}
86
+ </div>
87
+ </div>
88
+ </InputGroup>
89
+
90
+ {{!-- <ContentPanel @title="Availability" @open={{@options.catalog.hours.length}} @pad={{false}} @panelBodyClass="bg-white dark:bg-gray-800">
91
+ <div class="p-2">
92
+ <ScheduleManager @subject={{@options.catalog}} @subjectKey="catalog_uuid" @hourModelType="catalog-hour" class="grid grid-cols-1 gap-4 lg:grid-cols-2 lg:gap-2" />
93
+ </div>
94
+ </ContentPanel> --}}
95
+ </div>
96
+ </Modal::Default>
@@ -28,7 +28,12 @@
28
28
  @value={{@options.store.currency}}
29
29
  @helpText={{t "storefront.component.modals.create-first-store.select-currency"}}
30
30
  >
31
- <CurrencySelect @currency={{@options.store.currency}} @onCurrencyChange={{fn (mut @options.store.currency)}} @triggerClass="w-full form-select" @disabled={{cannot "storefront create store"}} />
31
+ <CurrencySelect
32
+ @currency={{@options.store.currency}}
33
+ @onCurrencyChange={{fn (mut @options.store.currency)}}
34
+ @triggerClass="w-full form-select"
35
+ @disabled={{cannot "storefront create store"}}
36
+ />
32
37
  </InputGroup>
33
38
  </div>
34
39
  </div>
@@ -0,0 +1,69 @@
1
+ <Modal::Default @modalIsOpened={{@modalIsOpened}} @options={{@options}} @confirm={{@onConfirm}} @decline={{@onDecline}}>
2
+ <div class="modal-body-container">
3
+ <InputGroup @name="Assign Food Truck Vehicle">
4
+ <ModelSelect
5
+ @modelName="vehicle"
6
+ @selectedModel={{@options.foodTruck.vehicle}}
7
+ @placeholder="Assign Food Truck Vehicle"
8
+ @triggerClass="form-select form-input"
9
+ @infiniteScroll={{false}}
10
+ @renderInPlace={{true}}
11
+ @onChange={{fn (mut @options.foodTruck.vehicle)}}
12
+ as |model|
13
+ >
14
+ <div class="flex flex-row">
15
+ <div class="mr-2">
16
+ <Image src={{model.photo_url}} class="w-10 h-10 rounded-md shadow-sm" />
17
+ </div>
18
+ <div class="flex flex-col flex-1">
19
+ <div class="font-semibold">{{model.display_name}}</div>
20
+ <div>{{model.plate_number}}</div>
21
+ </div>
22
+ </div>
23
+ </ModelSelect>
24
+ </InputGroup>
25
+ <InputGroup @name="Service Area">
26
+ <ModelSelect
27
+ @modelName="service-area"
28
+ @selectedModel={{@options.foodTruck.service_area}}
29
+ @placeholder="Assign Food Truck to Service Area"
30
+ @triggerClass="form-select form-input"
31
+ @infiniteScroll={{false}}
32
+ @renderInPlace={{true}}
33
+ @onChange={{fn (mut @options.foodTruck.service_area)}}
34
+ @allowClear={{true}}
35
+ as |model|
36
+ >
37
+ {{model.name}}
38
+ </ModelSelect>
39
+ </InputGroup>
40
+ {{#if @options.foodTruck.service_area}}
41
+ <InputGroup @name="Zone">
42
+ <ModelSelect
43
+ @modelName="zone"
44
+ @selectedModel={{@options.foodTruck.zone}}
45
+ @query={{hash service_area_uuid=@options.foodTruck.service_area.id}}
46
+ @placeholder="Assign Food Truck to Zone"
47
+ @triggerClass="form-select form-input"
48
+ @infiniteScroll={{false}}
49
+ @renderInPlace={{true}}
50
+ @onChange={{fn (mut @options.foodTruck.zone)}}
51
+ @allowClear={{true}}
52
+ as |model|
53
+ >
54
+ {{model.name}}
55
+ </ModelSelect>
56
+ </InputGroup>
57
+ {{/if}}
58
+ <InputGroup @name="Food Truck Status">
59
+ <Select
60
+ class="w-full"
61
+ @value={{@options.foodTruck.status}}
62
+ @placeholder="Select food truck status"
63
+ @options={{@options.statusOptions}}
64
+ @onSelect={{fn (mut @options.foodTruck.status)}}
65
+ @humanize={{true}}
66
+ />
67
+ </InputGroup>
68
+ </div>
69
+ </Modal::Default>
@@ -12,32 +12,18 @@ import { all } from 'rsvp';
12
12
  * This component uses various Ember services like store, internationalization, currentUser, modalsManager, and notifications for its operations.
13
13
  */
14
14
  export default class ModalsManageAddonsComponent extends Component {
15
- /** Service for data store operations. */
16
15
  @service store;
17
-
18
- /** Service for internationalization. */
19
16
  @service intl;
20
-
21
- /** Service for accessing current user information. */
22
17
  @service currentUser;
23
-
24
- /** Service for managing modal dialogs. */
25
18
  @service modalsManager;
26
-
27
- /** Service for displaying notifications. */
28
19
  @service notifications;
29
-
30
- /** Tracked array of addon categories. */
31
20
  @tracked categories = [];
32
-
33
- /** Tracked options object for the component. */
34
21
  @tracked options = {};
35
-
36
- /** The currently active store object. */
37
22
  @tracked activeStore;
38
23
 
39
24
  /**
40
25
  * Constructs the ModalsManageAddonsComponent instance with the given options.
26
+ *
41
27
  * @param {Object} owner - The owner of the instance.
42
28
  * @param {Object} options - Configuration options for the component.
43
29
  */
@@ -24,9 +24,9 @@
24
24
  {{#if this.order.isPickupReady}}
25
25
  <Button @size="xs" @type="success" @icon="check" @text={{t "storefront.component.widget.orders.mark-as-completed"}} @onClick={{fn this.markAsCompleted this.order}} />
26
26
  {{/if}}
27
- {{#unless this.order.isCanceled}}
27
+ {{#if (not (or this.order.isCanceled (eq this.order.status "order_canceled")))}}
28
28
  <Button @size="xs" @type="danger" @icon="ban" @helpText={{t "storefront.component.widget.orders.cancel-order"}} @onClick={{fn this.cancelOrder this.order}} />
29
- {{/unless}}
29
+ {{/if}}
30
30
  </div>
31
31
  </Overlay::Header>
32
32
  <Overlay::Body>
@@ -52,11 +52,11 @@
52
52
  <table class="storefront-widget-table">
53
53
  <thead>
54
54
  <tr class="h-12 text-left py-1">
55
- <th>{{t "storefront.common.id"}}</th>
56
- <th>{{t "storefront.common.name"}}</th>
57
- <th>{{t "storefront.common.phone"}}</th>
58
- <th>{{t "storefront.common.email"}}</th>
59
- <th>{{t "storefront.common.orders"}}</th>
55
+ <th style={{"width: 20%"}}>{{t "storefront.common.id"}}</th>
56
+ <th style={{"width: 15%"}}>{{t "storefront.common.name"}}</th>
57
+ <th style={{"width: 20%"}}>{{t "storefront.common.phone"}}</th>
58
+ <th style={{"width: 35%"}}>{{t "storefront.common.email"}}</th>
59
+ <th style={{"width: 10%"}}>{{t "storefront.common.orders"}}</th>
60
60
  <th></th>
61
61
  </tr>
62
62
  </thead>
@@ -30,6 +30,9 @@
30
30
  @pad={{false}}
31
31
  @wrapperClass={{@wrapperClass}}
32
32
  >
33
+ <div class="flex justify-end px-4 py-2">
34
+ <Toggle @label="Active Orders Filter" @isToggled={{this.activeFilter}} @onToggle={{this.toggleActiveFilter}} />
35
+ </div>
33
36
  {{#if (media "isMobile")}}
34
37
  <div class="flex flex-col p-3 space-y-3">
35
38
  {{#each this.orders as |order|}}
@@ -105,7 +108,7 @@
105
108
  @onClick={{fn this.markAsCompleted order}}
106
109
  />
107
110
  {{/if}}
108
- {{#unless order.isCanceled}}
111
+ {{#if (not (or order.isCanceled (eq order.status "order_canceled")))}}
109
112
  <Button
110
113
  @wrapperClass="w-full"
111
114
  class="btn-block w-full"
@@ -115,7 +118,7 @@
115
118
  @text={{t "storefront.component.widget.orders.cancel-order"}}
116
119
  @onClick={{fn this.cancelOrder order}}
117
120
  />
118
- {{/unless}}
121
+ {{/if}}
119
122
  </div>
120
123
  <ContentPanel @title={{t "storefront.component.widget.orders.more-details"}}>
121
124
  <div class="p-2 space-y-2">
@@ -279,7 +282,7 @@
279
282
  @onClick={{fn this.markAsCompleted order}}
280
283
  />
281
284
  {{/if}}
282
- {{#unless order.isCanceled}}
285
+ {{#if (not (or order.isCanceled (eq order.status "order_canceled")))}}
283
286
  <Button
284
287
  @size="xs"
285
288
  @type="danger"
@@ -287,7 +290,7 @@
287
290
  @helpText={{t "storefront.component.widget.orders.cancel-order"}}
288
291
  @onClick={{fn this.cancelOrder order}}
289
292
  />
290
- {{/unless}}
293
+ {{/if}}
291
294
  </div>
292
295
  </td>
293
296
  </tr>
@@ -19,6 +19,7 @@ export default class WidgetOrdersComponent extends Component {
19
19
  @tracked title = this.intl.t('storefront.component.widget.orders.widget-title');
20
20
  @tracked loaded = false;
21
21
  @tracked total = 0;
22
+ @tracked activeFilter = false;
22
23
 
23
24
  constructor(owner, { title }) {
24
25
  super(...arguments);
@@ -34,8 +35,13 @@ export default class WidgetOrdersComponent extends Component {
34
35
  });
35
36
  }
36
37
 
38
+ @action toggleActiveFilter(toggled) {
39
+ this.activeFilter = toggled;
40
+ this.loadOrders.perform();
41
+ }
42
+
37
43
  @action async reloadOrders(params = {}) {
38
- this.orders = await this.fetchOrders(params);
44
+ this.loadOrders.perform(params);
39
45
  }
40
46
 
41
47
  @task *loadOrders(params = {}) {
@@ -47,6 +53,10 @@ export default class WidgetOrdersComponent extends Component {
47
53
  ...params,
48
54
  };
49
55
 
56
+ if (this.activeFilter) {
57
+ queryParams.status = 'active';
58
+ }
59
+
50
60
  try {
51
61
  const orders = yield this.fetch.get('orders', queryParams, { namespace: 'storefront/int/v1', normalizeToEmberData: true });
52
62
  this.loaded = true;
@@ -0,0 +1,121 @@
1
+ import Controller from '@ember/controller';
2
+ import { tracked } from '@glimmer/tracking';
3
+ import { inject as service } from '@ember/service';
4
+ import { action } from '@ember/object';
5
+
6
+ export default class CatalogsIndexController extends Controller {
7
+ @service store;
8
+ @service intl;
9
+ @service storefront;
10
+ @service modalsManager;
11
+ @service notifications;
12
+ @service crud;
13
+ @service hostRouter;
14
+ @tracked statusOptions = ['draft', 'published'];
15
+
16
+ @action createCatalog() {
17
+ const catalog = this.store.createRecord('catalog', {
18
+ store_uuid: this.storefront.activeStore.id,
19
+ status: 'draft',
20
+ });
21
+
22
+ return this.editCatalog(catalog, {
23
+ title: 'New Catalog',
24
+ acceptButtonText: 'Confirm',
25
+ acceptButtonIcon: 'check',
26
+ confirm: async (modal) => {
27
+ modal.startLoading();
28
+
29
+ try {
30
+ await catalog.save();
31
+ this.hostRouter.refresh();
32
+ this.notifications.success('New catalog created.');
33
+ } catch (error) {
34
+ this.notifications.serverError(error);
35
+ } finally {
36
+ modal.stopLoading();
37
+ }
38
+ },
39
+ });
40
+ }
41
+
42
+ @action editCatalog(catalog, modalOptions = {}) {
43
+ const allProducts = this.store.query('product', { limit: -1 });
44
+
45
+ this.modalsManager.show('modals/create-catalog', {
46
+ title: 'Edit Food Truck',
47
+ acceptButtonText: 'Save Changes',
48
+ acceptButtonIcon: 'save',
49
+ statusOptions: this.statusOptions,
50
+ catalog,
51
+ allProducts,
52
+ selectedProducts: [],
53
+ toggleCategory: (category) => {
54
+ category.set('expanded', !category.get('expanded'));
55
+ },
56
+ finishAddingProducts: (category) => {
57
+ category.setProperties({
58
+ adding_products: false,
59
+ });
60
+ },
61
+ confirmSelectedProducts: (category, products) => {
62
+ category.setProperties({
63
+ products,
64
+ });
65
+ },
66
+ removeProduct: (category, product) => {
67
+ const products = category.get('products').filter((p) => p.id !== product.id);
68
+ category.set('products', products);
69
+ },
70
+ addProducts: (category) => {
71
+ category.setProperties({
72
+ expanded: true,
73
+ adding_products: true,
74
+ });
75
+ },
76
+ createCategory: () => {
77
+ const name = prompt('Enter category name');
78
+ const category = this.store.createRecord('catalog-category', { name });
79
+ catalog.categories.pushObject(category);
80
+ },
81
+ editCategory: (category) => {
82
+ const name = prompt('Change category name', category.name);
83
+ category.set('name', name);
84
+ },
85
+ deleteCategory: async (category) => {
86
+ const confirmed = confirm('Delete this category?');
87
+ if (confirmed) {
88
+ category.set('deleting', true);
89
+
90
+ try {
91
+ await category.destroyRecord();
92
+ } catch (error) {
93
+ this.notifications.serverError(error);
94
+ }
95
+ }
96
+ },
97
+ confirm: async (modal) => {
98
+ modal.startLoading();
99
+
100
+ try {
101
+ await catalog.save();
102
+ this.hostRouter.refresh();
103
+ this.notifications.success('Changes to catalog saved.');
104
+ } catch (error) {
105
+ this.notifications.serverError(error);
106
+ } finally {
107
+ modal.stopLoading();
108
+ }
109
+ },
110
+ ...modalOptions,
111
+ });
112
+ }
113
+
114
+ @action deleteCatalog(catalog) {
115
+ this.crud.delete(catalog, {
116
+ onSuccess: () => {
117
+ return this.hostRouter.refresh();
118
+ },
119
+ });
120
+ }
121
+ }
@@ -0,0 +1,100 @@
1
+ import Controller from '@ember/controller';
2
+ import { tracked } from '@glimmer/tracking';
3
+ import { inject as service } from '@ember/service';
4
+ import { action } from '@ember/object';
5
+
6
+ export default class FoodTrucksIndexController extends Controller {
7
+ @service store;
8
+ @service intl;
9
+ @service storefront;
10
+ @service modalsManager;
11
+ @service notifications;
12
+ @service crud;
13
+ @service hostRouter;
14
+ @tracked statusOptions = ['active', 'inactive'];
15
+
16
+ @action createFoodTruck() {
17
+ const foodTruck = this.store.createRecord('food-truck', {
18
+ store_uuid: this.storefront.activeStore.id,
19
+ status: 'active',
20
+ });
21
+
22
+ this.modalsManager.show('modals/create-food-truck', {
23
+ title: 'New Food Truck',
24
+ statusOptions: this.statusOptions,
25
+ foodTruck,
26
+ confirm: async (modal) => {
27
+ modal.startLoading();
28
+
29
+ try {
30
+ await foodTruck.save();
31
+ this.hostRouter.refresh();
32
+ this.notifications.success('New food truck created.');
33
+ } catch (error) {
34
+ this.notifications.serverError(error);
35
+ } finally {
36
+ modal.stopLoading();
37
+ }
38
+ },
39
+ });
40
+ }
41
+
42
+ @action editFoodTruck(foodTruck) {
43
+ this.modalsManager.show('modals/create-food-truck', {
44
+ title: 'Edit Food Truck',
45
+ acceptButtonText: 'Save Changes',
46
+ acceptButtonIcon: 'save',
47
+ statusOptions: this.statusOptions,
48
+ foodTruck,
49
+ confirm: async (modal) => {
50
+ modal.startLoading();
51
+
52
+ try {
53
+ await foodTruck.save();
54
+ this.hostRouter.refresh();
55
+ this.notifications.success('Changes to food truck saved.');
56
+ } catch (error) {
57
+ this.notifications.serverError(error);
58
+ } finally {
59
+ modal.stopLoading();
60
+ }
61
+ },
62
+ });
63
+ }
64
+
65
+ @action async assignCatalogs(foodTruck) {
66
+ const allCatalogs = await this.store.query('catalog', { limit: -1 });
67
+ console.log('[allCatalogs]', allCatalogs);
68
+ this.modalsManager.show('modals/assign-food-truck-catalogs', {
69
+ title: "Assign Catalog's to this Food Truck",
70
+ acceptButtonText: 'Done',
71
+ acceptButtonIcon: 'save',
72
+ foodTruck,
73
+ allCatalogs,
74
+ updateCatalogSelections: (catalogs) => {
75
+ foodTruck.set('catalogs', catalogs);
76
+ },
77
+ confirm: async (modal) => {
78
+ modal.startLoading();
79
+
80
+ try {
81
+ await foodTruck.save();
82
+ this.hostRouter.refresh();
83
+ this.notifications.success('Changes to food truck saved.');
84
+ } catch (error) {
85
+ this.notifications.serverError(error);
86
+ } finally {
87
+ modal.stopLoading();
88
+ }
89
+ },
90
+ });
91
+ }
92
+
93
+ @action deleteFoodTruck(foodTruck) {
94
+ this.crud.delete(foodTruck, {
95
+ onSuccess: () => {
96
+ return this.hostRouter.refresh();
97
+ },
98
+ });
99
+ }
100
+ }
@@ -0,0 +1,6 @@
1
+ import CategoryModel from '@fleetbase/console/models/category';
2
+ import { hasMany } from '@ember-data/model';
3
+
4
+ export default class CatalogCategoryModel extends CategoryModel {
5
+ @hasMany('products', { async: false, inverse: 'catalogCategories' }) products;
6
+ }
@@ -0,0 +1,72 @@
1
+ import Model, { attr, belongsTo } from '@ember-data/model';
2
+ import { computed } from '@ember/object';
3
+ import { format, formatDistanceToNow, parse, isValid } from 'date-fns';
4
+
5
+ export default class CatalogHourModel extends Model {
6
+ /** @ids */
7
+ @attr('string') catalog_uuid;
8
+
9
+ /** @relationships */
10
+ @belongsTo('catalog') catalog;
11
+
12
+ /** @attributes */
13
+ @attr('string') day_of_week;
14
+ @attr('string') start;
15
+ @attr('string') end;
16
+
17
+ /** @dates */
18
+ @attr('date') created_at;
19
+ @attr('date') updated_at;
20
+
21
+ /** @methods */
22
+ toJSON() {
23
+ return this.serialize();
24
+ }
25
+
26
+ /** @computed */
27
+ @computed('start') get startDateInstance() {
28
+ if (!this.start) {
29
+ return null;
30
+ }
31
+
32
+ const includesSeconds = this.start.split(':').length === 3;
33
+ const format = includesSeconds ? 'k:mm:ss' : 'k:mm';
34
+
35
+ return parse(this.start, format, new Date());
36
+ }
37
+
38
+ @computed('end') get endDateInstance() {
39
+ if (!this.end) {
40
+ return null;
41
+ }
42
+
43
+ const includesSeconds = this.end.split(':').length === 3;
44
+ const format = includesSeconds ? 'k:mm:ss' : 'k:mm';
45
+
46
+ return parse(this.end, format, new Date());
47
+ }
48
+
49
+ @computed('end', 'endDateInstance', 'start', 'startDateInstance') get humanReadableHours() {
50
+ if (!isValid(this.startDateInstance) || !isValid(this.endDateInstance)) {
51
+ return `${this.start} - ${this.end}`;
52
+ }
53
+
54
+ return `${format(this.startDateInstance, 'p')} - ${format(this.endDateInstance, 'p')}`;
55
+ }
56
+
57
+ get updatedAgo() {
58
+ return formatDistanceToNow(this.updated_at);
59
+ }
60
+
61
+ get updatedAt() {
62
+ return format(this.updated_at, 'PPP');
63
+ }
64
+
65
+ get createdAgo() {
66
+ return formatDistanceToNow(this.created_at);
67
+ }
68
+
69
+ get createdAt() {
70
+ return format(this.created_at, 'PPP p');
71
+ }
72
+ }