@fleetbase/storefront-engine 0.2.8 → 0.2.10

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.
@@ -0,0 +1,19 @@
1
+ <div class="storefront-key-metrics" ...attributes>
2
+ <div class="flex flex-col">
3
+ <div class="flex flex-row items-center justify-between px-4 py-2 border dark:border-gray-700 border-gray-200 dark:bg-gray-800 bg-gray-50 rounded-lg shadow-sm">
4
+ <span class="text-base font-bold text-black dark:text-gray-100">{{t "storefront.component.widget.key-metrics.title"}}</span>
5
+ </div>
6
+
7
+ <div class="mt-4">
8
+ {{#if this.getDashboardMetrics.isRunning}}
9
+ <Spinner />
10
+ {{else}}
11
+ <div class="grid grid-cols-2 lg:grid-cols-12 gap-4">
12
+ {{#each-in this.metrics as |title options|}}
13
+ <Dashboard::Count @title={{smart-humanize title}} @options={{options}} />
14
+ {{/each-in}}
15
+ </div>
16
+ {{/if}}
17
+ </div>
18
+ </div>
19
+ </div>
@@ -0,0 +1,73 @@
1
+ import Component from '@glimmer/component';
2
+ import { tracked } from '@glimmer/tracking';
3
+ import { inject as service } from '@ember/service';
4
+ import { task } from 'ember-concurrency-decorators';
5
+
6
+ export default class WidgetStorefrontKeyMetricsComponent extends Component {
7
+ /**
8
+ * Inject the fetch service.
9
+ *
10
+ * @memberof WidgetKeyMetricsComponent
11
+ */
12
+ @service fetch;
13
+
14
+ /**
15
+ * Property for loading metrics to.
16
+ *
17
+ * @memberof WidgetKeyMetricsComponent
18
+ */
19
+ @tracked metrics = {};
20
+
21
+ /**
22
+ * Creates an instance of WidgetKeyMetricsComponent.
23
+ * @memberof WidgetKeyMetricsComponent
24
+ */
25
+ constructor() {
26
+ super(...arguments);
27
+ this.getDashboardMetrics.perform();
28
+ }
29
+
30
+ /**
31
+ * Task which fetches key metrics.
32
+ *
33
+ * @memberof WidgetKeyMetricsComponent
34
+ */
35
+ @task *getDashboardMetrics() {
36
+ this.metrics = yield this.fetch.get('metrics', {}, { namespace: 'storefront/int/v1' }).then((response) => {
37
+ return this.createMetricsMapFromResponse(response);
38
+ });
39
+ }
40
+
41
+ /**
42
+ * Creates a map of metrics from the response data. This method organizes the metrics data into a more usable format.
43
+ *
44
+ * @param {Object} metrics - The metrics object fetched from the server.
45
+ * @returns {Object} A map of metrics where each key is a metric name and its value is an object of metric options.
46
+ * @memberof WidgetKeyMetricsComponent
47
+ */
48
+ createMetricsMapFromResponse(metrics = {}) {
49
+ const keys = Object.keys(metrics);
50
+ const map = {};
51
+
52
+ for (let i = 0; i < keys.length; i++) {
53
+ const key = keys[i];
54
+ map[key] = this.createMetricOptionsHash(key, metrics[key]);
55
+ }
56
+
57
+ return map;
58
+ }
59
+
60
+ /**
61
+ * Creates a hash of options for a given metric. Depending on the metric key, it assigns a specific format.
62
+ *
63
+ * @param {string} key - The key representing the specific metric.
64
+ * @param {number} value - The value of the metric.
65
+ * @returns {Object} An object containing the metric value and its format.
66
+ * @memberof WidgetKeyMetricsComponent
67
+ */
68
+ createMetricOptionsHash(key, value) {
69
+ const options = { value };
70
+
71
+ return options;
72
+ }
73
+ }
@@ -16,8 +16,8 @@ export default class WidgetStorefrontMetricsComponent extends Component {
16
16
  };
17
17
 
18
18
  @tracked isLoading = true;
19
- @tracked start = format(startOfMonth(new Date()), 'P');
20
- @tracked end = format(endOfMonth(new Date()), 'P');
19
+ @tracked start = format(startOfMonth(new Date()), 'yyyy-MM-dd');
20
+ @tracked end = format(endOfMonth(new Date()), 'yyyy-MM-dd');
21
21
 
22
22
  @computed('args.title') get title() {
23
23
  return this.args.title || 'This Month';
@@ -95,14 +95,15 @@ export default class ProductsIndexCategoryNewController extends BaseController {
95
95
  return;
96
96
  }
97
97
 
98
+ // Queue and upload immediatley
98
99
  this.uploadQueue.pushObject(file);
99
100
  this.fetch.uploadFile.perform(
100
101
  file,
101
102
  {
102
103
  path: `uploads/storefront/${this.activeStore.id}/products`,
103
104
  subject_uuid: this.product.id,
104
- subject_type: `storefront:product`,
105
- type: `storefront_product`,
105
+ subject_type: 'storefront:product',
106
+ type: 'storefront_product',
106
107
  },
107
108
  (uploadedFile) => {
108
109
  this.product.files.pushObject(uploadedFile);
@@ -116,6 +117,10 @@ export default class ProductsIndexCategoryNewController extends BaseController {
116
117
  },
117
118
  () => {
118
119
  this.uploadQueue.removeObject(file);
120
+ // remove file from queue
121
+ if (file.queue && typeof file.queue.remove === 'function') {
122
+ file.queue.remove(file);
123
+ }
119
124
  }
120
125
  );
121
126
  }
package/addon/engine.js CHANGED
@@ -3,6 +3,7 @@ import loadInitializers from 'ember-load-initializers';
3
3
  import Resolver from 'ember-resolver';
4
4
  import config from './config/environment';
5
5
  import services from '@fleetbase/ember-core/exports/services';
6
+ import StorefrontKeyMetricsWidget from './components/widget/storefront-key-metrics';
6
7
 
7
8
  const { modulePrefix } = config;
8
9
  const externalRoutes = ['console', 'extensions'];
@@ -17,6 +18,23 @@ export default class StorefrontEngine extends Engine {
17
18
  setupExtension = function (app, engine, universe) {
18
19
  // register menu item in header
19
20
  universe.registerHeaderMenuItem('Storefront', 'console.storefront', { icon: 'store', priority: 1 });
21
+
22
+ // widgets for registry
23
+ const KeyMetricsWidgetDefinition = {
24
+ did: 'storefront-metrics',
25
+ name: 'Storefront Metrics',
26
+ description: 'Key metrics from Storefront.',
27
+ icon: 'store',
28
+ component: StorefrontKeyMetricsWidget,
29
+ grid_options: { w: 12, h: 7, minW: 8, minH: 7 },
30
+ options: {
31
+ title: 'Storefront Metrics',
32
+ },
33
+ };
34
+
35
+ // register widgets
36
+ universe.registerDefaultDashboardWidgets([KeyMetricsWidgetDefinition]);
37
+ universe.registerDashboardWidgets([KeyMetricsWidgetDefinition]);
20
38
  };
21
39
  }
22
40
 
@@ -33,5 +33,6 @@
33
33
  </div>
34
34
 
35
35
  <div>{{outlet}}</div>
36
+ <Spacer @height="300px" />
36
37
  </Overlay::Body>
37
38
  </Overlay>
@@ -214,7 +214,7 @@
214
214
  class="min-h-56 dropzone w-full rounded-lg px-4 py-8 min-h bg-gray-50 dark:bg-gray-900 bg-opacity-25 text-gray-900 dark:text-white text-center flex flex-col items-center justify-center border-2 border-dashed border-gray-200 dark:border-indigo-500"
215
215
  >
216
216
  <div class="flex items-center justify-center py-5">
217
- <Spinner class="text-sm dar:text-gray-100" @loadingMessage="Uploading..." />
217
+ <Spinner class="text-sm dar:text-gray-100" @loadingMessage={{t "component.dropzone.uploading"}} />
218
218
  </div>
219
219
  </div>
220
220
  {{else}}
@@ -222,28 +222,27 @@
222
222
  <FileDropzone @queue={{queue}} class="dropzone file-dropzone" as |dropzone|>
223
223
  {{#if dropzone.active}}
224
224
  {{#if dropzone.valid}}
225
- Drop to upload
225
+ {{t "component.dropzone.drop-to-upload"}}
226
226
  {{else}}
227
- Invalid
227
+ {{t "component.dropzone.invalid"}}
228
228
  {{/if}}
229
229
  {{else if queue.files.length}}
230
230
  <div class="my-2">
231
231
  <FaIcon @icon="photo-video" class="text-indigo-500 mr-2" />
232
- {{pluralize queue.files.length "file"}}
233
- ready for upload.
232
+ {{t "component.dropzone.files-ready-for-upload" numOfFiles=(pluralize queue.files.length (t "component.dropzone.file"))}}
234
233
  </div>
235
234
  <div class="my-2">({{queue.progress}}%)</div>
236
235
  {{else}}
237
236
  <h4 class="font-semibold mb-8">
238
237
  <FaIcon @icon="photo-video" @size="2x" class="text-indigo-500 mr-2" />
239
- Upload Images & Videos
238
+ {{t "component.dropzone.upload-images-videos"}}
240
239
  </h4>
241
240
  <div>
242
241
  {{#if dropzone.supported}}
243
- <p class="text-base font-semibold my-5">Drag and drop image and video files onto this dropzone</p>
242
+ <p class="text-base font-semibold my-5">{{t "component.dropzone.dropzone-supported-images-videos"}}</p>
244
243
  {{/if}}
245
244
  <FileUpload @name="files" @for="files" @accept={{join "," this.acceptedFileTypes}} @multiple={{true}} @onFileAdded={{this.queueFile}}>
246
- <a tabindex={{0}} class="btn btn-magic cursor-pointer ml-1">or select files to upload.</a>
245
+ <a tabindex={{0}} class="btn btn-magic cursor-pointer ml-1">{{t "component.dropzone.or-select-button-text"}}</a>
247
246
  </FileUpload>
248
247
  </div>
249
248
  {{/if}}
@@ -252,15 +251,15 @@
252
251
  {{#if this.uploadQueue}}
253
252
  <div class="mx-4">
254
253
  <div class="flex items-center justify-between mb-4">
255
- <span class="leading-6 dark:text-gray-100">Upload Queue</span>
254
+ <span class="leading-6 dark:text-gray-100">{{t "component.dropzone.upload-queue"}}</span>
256
255
  </div>
257
256
  <div class="space-y-2 mb-4">
258
257
  {{#each this.uploadQueue as |file|}}
259
- <div class="flex items-center justify-between bg-green-100 border border-green-800 dark:border-green-800 py-1.5 shadow-sm rounded-lg px-4">
260
- <div class="text-sm text-green-900">{{file.name}}</div>
258
+ <div class="flex items-center justify-between bg-blue-100 border border-blue-800 dark:border-blue-800 py-1.5 shadow-sm rounded-lg px-4">
259
+ <div class="text-xs text-blue-900 truncate">{{truncate-filename file.name 50}}</div>
261
260
  <div class="flex items-center text-sm">
262
- <Spinner class="text-green-900 mr-2" />
263
- <span class="font-bold text-green-900">{{round file.progress}}%</span>
261
+ <Spinner class="text-blue-900 mr-2" />
262
+ <span class="font-bold text-blue-900">{{round file.progress}}%</span>
264
263
  </div>
265
264
  </div>
266
265
  {{/each}}
@@ -297,5 +296,6 @@
297
296
  </ArrayInput>
298
297
  </div>
299
298
  </ContentPanel>
299
+ <Spacer @height="300px" />
300
300
  </Overlay::Body>
301
301
  </Overlay>
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/storefront-engine/components/widget/storefront-key-metrics';
package/composer.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fleetbase/storefront-api",
3
- "version": "0.2.8",
3
+ "version": "0.2.10",
4
4
  "description": "Headless Commerce & Marketplace Extension for Fleetbase",
5
5
  "keywords": [
6
6
  "fleetbase-extension",
@@ -21,22 +21,22 @@
21
21
  }
22
22
  ],
23
23
  "require": {
24
- "php": "^7.4|^8.0",
25
- "fleetbase/core-api": "^1.3.12",
26
- "fleetbase/fleetops-api": "^0.4.1",
24
+ "php": "^8.0",
25
+ "fleetbase/core-api": "^1.4.0",
26
+ "fleetbase/fleetops-api": "^0.4.5",
27
27
  "geocoder-php/google-maps-places-provider": "^1.4",
28
- "laravel-notification-channels/apn": "^3.8",
29
- "laravel-notification-channels/fcm": "^2.7",
28
+ "laravel-notification-channels/apn": "^5.0",
29
+ "laravel-notification-channels/fcm": "^4.1",
30
30
  "laravel-notification-channels/twilio": "^3.3",
31
- "milon/barcode": "^9.0",
31
+ "milon/barcode": "^10.0",
32
32
  "php-http/guzzle7-adapter": "^1.0",
33
33
  "psr/http-factory-implementation": "*",
34
34
  "toin0u/geocoder-laravel": "^4.4"
35
35
  },
36
36
  "require-dev": {
37
37
  "friendsofphp/php-cs-fixer": "^3.34.1",
38
- "nunomaduro/collision": "^5.11.0|^6.4.0",
39
- "pestphp/pest": "^1.22.6",
38
+ "nunomaduro/collision": "^7.0",
39
+ "pestphp/pest": "^2.33.2",
40
40
  "phpstan/phpstan": "^1.10.38",
41
41
  "symfony/var-dumper": "^5.4.29"
42
42
  },
package/extension.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "Storefront",
3
- "version": "0.2.8",
3
+ "version": "0.2.10",
4
4
  "description": "Headless Commerce & Marketplace Extension for Fleetbase",
5
5
  "repository": "https://github.com/fleetbase/storefront",
6
6
  "license": "MIT",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fleetbase/storefront-engine",
3
- "version": "0.2.8",
3
+ "version": "0.2.10",
4
4
  "description": "Headless Commerce & Marketplace Extension for Fleetbase",
5
5
  "fleetbase": {
6
6
  "route": "storefront",
@@ -43,9 +43,9 @@
43
43
  "publish:github": "npm config set '@fleetbase:registry' https://npm.pkg.github.com/ && npm publish"
44
44
  },
45
45
  "dependencies": {
46
- "@fleetbase/ember-core": "^0.2.0",
47
- "@fleetbase/ember-ui": "^0.2.9",
48
- "@fleetbase/fleetops-data": "^0.1.7",
46
+ "@fleetbase/ember-core": "^0.2.3",
47
+ "@fleetbase/ember-ui": "^0.2.10",
48
+ "@fleetbase/fleetops-data": "^0.1.8",
49
49
  "@babel/core": "^7.23.2",
50
50
  "@fortawesome/ember-fontawesome": "^0.4.1",
51
51
  "@fortawesome/fontawesome-svg-core": "^6.4.0",
@@ -11,11 +11,6 @@ use Illuminate\Support\Carbon;
11
11
 
12
12
  class ActionController extends Controller
13
13
  {
14
- public function welcome()
15
- {
16
- return response()->json(['message' => 'Hello Friend!']);
17
- }
18
-
19
14
  /**
20
15
  * Get the number of storefronts created.
21
16
  *
@@ -53,15 +48,10 @@ class ActionController extends Controller
53
48
  }
54
49
 
55
50
  $store = Store::where('uuid', $store)->first();
56
-
57
51
  if (!$store) {
58
52
  return response()->json($metrics);
59
53
  }
60
54
 
61
- /*
62
- * Query metrics between given time period
63
- */
64
-
65
55
  // send back currency
66
56
  $metrics['currency'] = $store->currency;
67
57
 
@@ -102,16 +92,6 @@ class ActionController extends Controller
102
92
  return $order->transaction->amount;
103
93
  });
104
94
 
105
- response()->json($metrics);
106
- }
107
-
108
- /**
109
- * CORS OPTIONS.
110
- *
111
- * @return \Illuminate\Http\Response
112
- */
113
- public function options()
114
- {
115
- return response()->json(['status' => 'ok']);
95
+ return response()->json($metrics);
116
96
  }
117
97
  }
@@ -8,6 +8,12 @@ use Illuminate\Http\Request;
8
8
 
9
9
  class MetricsController extends Controller
10
10
  {
11
+ /**
12
+ * Get all key metrics for a companies storefront
13
+ *
14
+ * @param \Illuminate\Http\Request $request
15
+ * @return \Illuminate\Http\Response
16
+ */
11
17
  public function all(Request $request)
12
18
  {
13
19
  $start = $request->date('start');
@@ -22,50 +28,4 @@ class MetricsController extends Controller
22
28
 
23
29
  return response()->json($data);
24
30
  }
25
-
26
- public function dashboard(Request $request)
27
- {
28
- $start = $request->date('start');
29
- $end = $request->date('end');
30
- $discover = $request->array('discover', []);
31
- $metrics = [];
32
-
33
- try {
34
- $metrics = Metrics::forCompany($request->user()->company, $start, $end)->with($discover)->get();
35
- } catch (\Exception $e) {
36
- return response()->error($e->getMessage());
37
- }
38
-
39
- // metrics format map
40
- $metricsFormats = [];
41
-
42
- // dashboard config
43
- $dashboardConfig = [
44
- [
45
- 'size' => 12,
46
- 'title' => 'Storefront Metrics',
47
- 'classList' => [],
48
- 'component' => null,
49
- 'queryParams' => [
50
- 'start' => ['component' => 'date-picker'],
51
- 'end' => ['component' => 'date-picker'],
52
- ],
53
- 'widgets' => collect($metrics)
54
- ->map(function ($value, $key) use ($metricsFormats) {
55
- return [
56
- 'component' => 'count',
57
- 'options' => [
58
- 'format' => $metricsFormats[$key] ?? null,
59
- 'title' => str_replace('_', ' ', \Illuminate\Support\Str::title($key)),
60
- 'value' => $value,
61
- ],
62
- ];
63
- })
64
- ->values()
65
- ->toArray(),
66
- ],
67
- ];
68
-
69
- return response()->json(array_values($dashboardConfig));
70
- }
71
31
  }
@@ -9,7 +9,7 @@ use Fleetbase\Storefront\Http\Resources\Store as StorefrontStore;
9
9
  use Fleetbase\Storefront\Http\Resources\StoreLocation as StorefrontStoreLocation;
10
10
  use Fleetbase\Storefront\Models\Store;
11
11
  use Fleetbase\Storefront\Models\StoreLocation;
12
- use Grimzy\LaravelMysqlSpatial\Types\Point;
12
+ use Fleetbase\LaravelMysqlSpatial\Types\Point;
13
13
  use Illuminate\Http\Request;
14
14
 
15
15
  class NetworkController extends Controller
@@ -30,9 +30,9 @@ class Category extends FleetbaseResource
30
30
  ),
31
31
  'tags' => $this->tags ?? [],
32
32
  'translations' => $this->translations ?? [],
33
- 'products' => Product::collection($this->whenLoaded('products')),
33
+ 'products' => $this->when($request->has('with_products') || $request->inArray('with', 'products'), Product::collection($this->products)),
34
34
  'subcategories' => $this->when(
35
- $request->has('with_subcategories'),
35
+ $request->has('with_subcategories') || $request->inArray('with', 'subcategories'),
36
36
  array_map(
37
37
  function ($subCategory) {
38
38
  return new Category($subCategory);
@@ -43,10 +43,10 @@ class Store extends FleetbaseResource
43
43
  'online' => $this->online,
44
44
  'is_network' => false,
45
45
  'is_store' => true,
46
- 'category' => $this->when($request->filled('network') && $request->has('with_category'), new Category($this->getNetworkCategoryUsingId($request->input('network')))),
46
+ 'category' => $this->when($request->filled('network') && ($request->has('with_category') || $request->inArray('with', 'category')), new Category($this->getNetworkCategoryUsingId($request->input('network')))),
47
47
  'networks' => $this->when($request->boolean('with_networks') || $request->inArray('with', 'networks'), Network::collection($this->networks)),
48
- 'locations' => $this->when($request->boolean('with_locations'), $this->locations->mapInto(StoreLocation::class)),
49
- 'media' => $this->when($request->boolean('with_media'), Media::collection($this->media)),
48
+ 'locations' => $this->when($request->boolean('with_locations') || $request->inArray('with', 'locations'), $this->locations->mapInto(StoreLocation::class)),
49
+ 'media' => $this->when($request->boolean('with_media') || $request->inArray('with', 'media'), Media::collection($this->media)),
50
50
  'slug' => $this->slug,
51
51
  'created_at' => $this->created_at,
52
52
  'updated_at' => $this->updated_at,
@@ -6,11 +6,13 @@ use Fleetbase\Models\Category;
6
6
  use Fleetbase\Traits\HasApiModelBehavior;
7
7
  use Fleetbase\Traits\HasUuid;
8
8
  use Illuminate\Database\Eloquent\Relations\Pivot;
9
+ use Illuminate\Database\Eloquent\SoftDeletes;
9
10
 
10
11
  class NetworkStore extends Pivot
11
12
  {
12
13
  use HasUuid;
13
14
  use HasApiModelBehavior;
15
+ use SoftDeletes;
14
16
 
15
17
  /**
16
18
  * The database table used by the model.
@@ -7,7 +7,7 @@ use Fleetbase\Models\User;
7
7
  use Fleetbase\Traits\HasApiModelBehavior;
8
8
  use Fleetbase\Traits\HasPublicid;
9
9
  use Fleetbase\Traits\HasUuid;
10
- use Grimzy\LaravelMysqlSpatial\Eloquent\SpatialTrait;
10
+ use Fleetbase\LaravelMysqlSpatial\Eloquent\SpatialTrait;
11
11
 
12
12
  class StoreLocation extends StorefrontModel
13
13
  {
@@ -121,7 +121,7 @@ class StoreLocation extends StorefrontModel
121
121
  }
122
122
 
123
123
  /**
124
- * @return \Grimzy\LaravelMysqlSpatial\Types\Point
124
+ * @return \Fleetbase\LaravelMysqlSpatial\Types\Point
125
125
  */
126
126
  public function getLocationAttribute()
127
127
  {
@@ -112,7 +112,6 @@ Route::prefix(config('storefront.api.routing.prefix', 'storefront'))->namespace(
112
112
  $router->group(
113
113
  ['prefix' => 'v1', 'middleware' => ['fleetbase.protected']],
114
114
  function ($router) {
115
- $router->get('/', 'ActionController@welcome');
116
115
  $router->group(
117
116
  ['prefix' => 'actions'],
118
117
  function ($router) {
@@ -161,13 +160,10 @@ Route::prefix(config('storefront.api.routing.prefix', 'storefront'))->namespace(
161
160
  $router->group(
162
161
  [],
163
162
  function ($router) {
164
- /* Dashboard Build */
165
- $router->get('dashboard', 'MetricsController@dashboard');
166
-
167
163
  $router->group(
168
164
  ['prefix' => 'metrics'],
169
165
  function ($router) {
170
- $router->get('all', 'MetricsController@all');
166
+ $router->get('/', 'MetricsController@all');
171
167
  }
172
168
  );
173
169
  }
@@ -212,6 +212,8 @@ storefront:
212
212
  security-access-code: Security Access Code
213
213
  postal-code: Postal Code
214
214
  widget:
215
+ key-metrics:
216
+ title: Storefront Metrics
215
217
  customers:
216
218
  widget-title: Recent Customers
217
219
  phone: Phone