contentoh-components-library 21.6.62 → 21.6.64

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.
@@ -11,8 +11,10 @@ var _jsxRuntime = require("react/jsx-runtime");
11
11
 
12
12
  var MetricCard = function MetricCard(_ref) {
13
13
  var label = _ref.label,
14
- number = _ref.number;
14
+ number = _ref.number,
15
+ hint = _ref.hint;
15
16
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_styles.Container, {
17
+ title: hint,
16
18
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("p", {
17
19
  children: label
18
20
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)("p", {
@@ -5,7 +5,7 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
5
5
  Object.defineProperty(exports, "__esModule", {
6
6
  value: true
7
7
  });
8
- exports.default = exports.DashboardMetricDefault = void 0;
8
+ exports.default = exports.DashboardMetricDoughnut = exports.DashboardMetricDefault = void 0;
9
9
 
10
10
  var _objectSpread2 = _interopRequireDefault(require("@babel/runtime/helpers/esm/objectSpread2"));
11
11
 
@@ -42,4 +42,14 @@ DashboardMetricDefault.args = {
42
42
  RC: 11,
43
43
  RP: 49
44
44
  }
45
- };
45
+ };
46
+ var DashboardMetricDoughnut = Template.bind({});
47
+ exports.DashboardMetricDoughnut = DashboardMetricDoughnut;
48
+ DashboardMetricDoughnut.args = (0, _objectSpread2.default)((0, _objectSpread2.default)({}, DashboardMetricDefault.args), {}, {
49
+ type: "doughnut",
50
+ displayLegend: true,
51
+ showPercent: true,
52
+ displayScale: false,
53
+ total: 1610,
54
+ totalFiltered: 1610
55
+ });
@@ -11,6 +11,8 @@ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/es
11
11
 
12
12
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/esm/slicedToArray"));
13
13
 
14
+ var _react = require("react");
15
+
14
16
  var _styles = require("./styles");
15
17
 
16
18
  var _Graphic = require("../../atoms/Graphic");
@@ -41,10 +43,34 @@ var DashboardMetric = function DashboardMetric(_ref) {
41
43
  borderColor = _ref.borderColor,
42
44
  total = _ref.total,
43
45
  totalFiltered = _ref.totalFiltered;
44
- var labels = type === "doughnut" ? Object.entries(dataObject).map(function (_ref2) {
45
- var _ref3 = (0, _slicedToArray2.default)(_ref2, 2),
46
- key = _ref3[0],
47
- value = _ref3[1];
46
+ // Chart.js coloca la leyenda por opción, no por CSS. Se mide el panel y no
47
+ // la ventana, porque el ancho real depende también del menú de la app: a
48
+ // 828px de ventana el panel queda en 331 y la leyenda de lado sale cortada.
49
+ var containerRef = (0, _react.useRef)(null);
50
+
51
+ var _useState = (0, _react.useState)(false),
52
+ _useState2 = (0, _slicedToArray2.default)(_useState, 2),
53
+ isNarrow = _useState2[0],
54
+ setIsNarrow = _useState2[1];
55
+
56
+ (0, _react.useEffect)(function () {
57
+ var node = containerRef.current;
58
+ if (!node || typeof ResizeObserver === "undefined") return undefined;
59
+ var observer = new ResizeObserver(function (_ref2) {
60
+ var _ref3 = (0, _slicedToArray2.default)(_ref2, 1),
61
+ entry = _ref3[0];
62
+
63
+ return setIsNarrow(entry.contentRect.width < 420);
64
+ });
65
+ observer.observe(node);
66
+ return function () {
67
+ return observer.disconnect();
68
+ };
69
+ }, []);
70
+ var labels = type === "doughnut" ? Object.entries(dataObject).map(function (_ref4) {
71
+ var _ref5 = (0, _slicedToArray2.default)(_ref4, 2),
72
+ key = _ref5[0],
73
+ value = _ref5[1];
48
74
 
49
75
  return key !== "NS" && "".concat((0, _dashboardMetricUtils.getFullStatus)(key), " (").concat(value, ")");
50
76
  }) : Object.keys(dataObject);
@@ -74,11 +100,11 @@ var DashboardMetric = function DashboardMetric(_ref) {
74
100
  plugins: {
75
101
  tooltip: {
76
102
  callbacks: {
77
- label: function label(_ref4) {
103
+ label: function label(_ref6) {
78
104
  var _dataset$data;
79
105
 
80
- var dataset = _ref4.dataset,
81
- element = _ref4.element;
106
+ var dataset = _ref6.dataset,
107
+ element = _ref6.element;
82
108
  var sum = dataset === null || dataset === void 0 ? void 0 : (_dataset$data = dataset.data) === null || _dataset$data === void 0 ? void 0 : _dataset$data.reduce(function (accumulator, value) {
83
109
  return accumulator + value;
84
110
  }, 0);
@@ -90,7 +116,7 @@ var DashboardMetric = function DashboardMetric(_ref) {
90
116
  }
91
117
  },
92
118
  legend: {
93
- position: "right",
119
+ position: isNarrow ? "bottom" : "right",
94
120
  display: displayLegend,
95
121
  labels: {
96
122
  usePointStyle: true,
@@ -113,6 +139,8 @@ var DashboardMetric = function DashboardMetric(_ref) {
113
139
  showTooltips: true
114
140
  };
115
141
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_styles.Container, {
142
+ ref: containerRef,
143
+ className: isNarrow ? "is-narrow" : "",
116
144
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
117
145
  className: "text-container",
118
146
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("p", {
@@ -15,6 +15,6 @@ var _variables = require("../../../global-files/variables");
15
15
 
16
16
  var _templateObject;
17
17
 
18
- var Container = _styledComponents.default.div(_templateObject || (_templateObject = (0, _taggedTemplateLiteral2.default)(["\n border: 1px solid ", ";\n border-radius: 5px;\n padding: 24px;\n position: relative;\n\n .text-container {\n p {\n font-family: ", ";\n color: ", ";\n font-size: 17px;\n\n &:last-of-type {\n font-size: 12px;\n color: ", ";\n }\n\n & + * {\n margin-top: 5px;\n }\n }\n\n & + * {\n margin-top: 15px;\n }\n }\n\n .select-container {\n display: flex;\n\n .select {\n flex: 0.1 1 30%;\n\n select {\n width: 100%;\n }\n\n & + * {\n margin-left: 10px;\n }\n }\n\n & + * {\n margin-top: 10px;\n }\n }\n\n .graphic-cotainer {\n height: 350px;\n border: 1px solid ", ";\n padding: 10px;\n border-radius: 5px;\n\n div {\n height: fill-available;\n }\n }\n\n & > p {\n font-family: ", ";\n color: ", ";\n font-size: 17px;\n\n &.x-label {\n position: absolute;\n left: 50%;\n transform: translate(-50%);\n bottom: 4px;\n }\n\n &.y-label {\n position: absolute;\n transform: rotate(-90deg);\n left: -28px;\n top: 50%;\n }\n\n &.center-label {\n position: absolute;\n top: 52%;\n right: 58%;\n font-size: 28px;\n color: #000;\n font-feature-settings: \"pnum\", \"lnum\";\n font-family: ", ";\n }\n }\n"])), _variables.GlobalColors.s3, _variables.FontFamily.Raleway, _variables.GlobalColors.s5, _variables.GlobalColors.s4, _variables.GlobalColors.s3, _variables.FontFamily.Raleway, _variables.GlobalColors.s5, _variables.FontFamily.Raleway_600);
18
+ var Container = _styledComponents.default.div(_templateObject || (_templateObject = (0, _taggedTemplateLiteral2.default)(["\n border: 1px solid ", ";\n border-radius: 5px;\n padding: 24px;\n position: relative;\n\n @media (max-width: 600px) {\n padding: 12px;\n\n .graphic-cotainer {\n padding: 5px;\n }\n\n /* Va rotada y fuera del recuadro; con el padding chico se encima. */\n > p.y-label {\n display: none;\n }\n }\n\n .text-container {\n p {\n font-family: ", ";\n color: ", ";\n font-size: 17px;\n\n &:last-of-type {\n font-size: 12px;\n color: ", ";\n }\n\n & + * {\n margin-top: 5px;\n }\n }\n\n & + * {\n margin-top: 15px;\n }\n }\n\n .select-container {\n display: flex;\n\n .select {\n flex: 0.1 1 30%;\n\n select {\n width: 100%;\n }\n\n & + * {\n margin-left: 10px;\n }\n }\n\n & + * {\n margin-top: 10px;\n }\n }\n\n /* Con la leyenda abajo, Chart.js le da un m\xE1ximo de la altura del recuadro\n y descarta las entradas que no caben; por eso crece junto con ella. */\n &.is-narrow .graphic-cotainer {\n height: 500px;\n }\n\n .graphic-cotainer {\n height: 350px;\n border: 1px solid ", ";\n padding: 10px;\n border-radius: 5px;\n\n div {\n height: fill-available;\n }\n }\n\n & > p {\n font-family: ", ";\n color: ", ";\n font-size: 17px;\n\n &.x-label {\n position: absolute;\n left: 50%;\n transform: translate(-50%);\n bottom: 4px;\n }\n\n &.y-label {\n position: absolute;\n transform: rotate(-90deg);\n left: -28px;\n top: 50%;\n }\n\n &.center-label {\n position: absolute;\n top: 52%;\n right: 58%;\n font-size: 28px;\n color: #000;\n font-feature-settings: \"pnum\", \"lnum\";\n font-family: ", ";\n }\n }\n"])), _variables.GlobalColors.s3, _variables.FontFamily.Raleway, _variables.GlobalColors.s5, _variables.GlobalColors.s4, _variables.GlobalColors.s3, _variables.FontFamily.Raleway, _variables.GlobalColors.s5, _variables.FontFamily.Raleway_600);
19
19
 
20
20
  exports.Container = Container;
@@ -111,9 +111,9 @@ exports.getCategories = getCategories;
111
111
  var formatDate = function formatDate(date) {
112
112
  var newDate = new Date(date);
113
113
  var year = newDate.getFullYear();
114
- var month = newDate.getMonth() + 1;
115
- var day = newDate.getDate();
116
- return "".concat(year, "/").concat(month, "/").concat(day);
114
+ var month = "".concat(newDate.getMonth() + 1).padStart(2, "0");
115
+ var day = "".concat(newDate.getDate()).padStart(2, "0");
116
+ return "".concat(year, "-").concat(month, "-").concat(day);
117
117
  };
118
118
 
119
119
  exports.formatDate = formatDate;
@@ -32,7 +32,7 @@ var _axios = _interopRequireDefault(require("axios"));
32
32
  var _jsxRuntime = require("react/jsx-runtime");
33
33
 
34
34
  var Dashboard = function Dashboard(_ref) {
35
- var _company$financedReta, _metricsData$;
35
+ var _company$financedReta;
36
36
 
37
37
  var jwt = _ref.jwt,
38
38
  user = _ref.user,
@@ -135,21 +135,17 @@ var Dashboard = function Dashboard(_ref) {
135
135
  var _useState31 = (0, _react.useState)(0),
136
136
  _useState32 = (0, _slicedToArray2.default)(_useState31, 2),
137
137
  totalCount = _useState32[0],
138
- setTotalCount = _useState32[1];
138
+ setTotalCount = _useState32[1]; // Total sin filtros, solo para el porcentaje de la dona.
139
139
 
140
- var _useState33 = (0, _react.useState)((company === null || company === void 0 ? void 0 : (_company$financedReta = company.financedRetailers) === null || _company$financedReta === void 0 ? void 0 : _company$financedReta.length) > 0),
141
- _useState34 = (0, _slicedToArray2.default)(_useState33, 1),
142
- isProviderFinanced = _useState34[0];
143
140
 
144
- var _useState35 = (0, _react.useState)([]),
145
- _useState36 = (0, _slicedToArray2.default)(_useState35, 2),
146
- trades = _useState36[0],
147
- setTrades = _useState36[1];
141
+ var _useState33 = (0, _react.useState)(0),
142
+ _useState34 = (0, _slicedToArray2.default)(_useState33, 2),
143
+ totalUnfiltered = _useState34[0],
144
+ setTotalUnfiltered = _useState34[1];
148
145
 
149
- var _useState37 = (0, _react.useState)([]),
150
- _useState38 = (0, _slicedToArray2.default)(_useState37, 2),
151
- tradesSelected = _useState38[0],
152
- setTradesSelected = _useState38[1];
146
+ var _useState35 = (0, _react.useState)((company === null || company === void 0 ? void 0 : (_company$financedReta = company.financedRetailers) === null || _company$financedReta === void 0 ? void 0 : _company$financedReta.length) > 0),
147
+ _useState36 = (0, _slicedToArray2.default)(_useState35, 1),
148
+ isProviderFinanced = _useState36[0];
153
149
 
154
150
  var loadProductVersions = /*#__PURE__*/function () {
155
151
  var _ref2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(queryObject) {
@@ -241,71 +237,99 @@ var Dashboard = function Dashboard(_ref) {
241
237
 
242
238
  var loadProductsByStatus = /*#__PURE__*/function () {
243
239
  var _ref4 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee3(orderByStatus) {
244
- var isRetailer, isProvider, isCollaborator, _ref5, ordersBydate, ordersByStatus, ordersByActive, companyTrades, _ordersByStatus$total, total, _ordersByStatus$R, R, _ordersByStatus$PA, PA, _ordersByStatus$Ex, Ex, _ordersByStatus$ACA, ACA, productsFinished, inProcess, registeredProducts, metricsArray;
240
+ var _ordersByActive$regis;
241
+
242
+ var isBaseline,
243
+ isRetailer,
244
+ isProvider,
245
+ isCollaborator,
246
+ _ref5,
247
+ ordersBydate,
248
+ _ref5$ordersByStatus,
249
+ ordersByStatus,
250
+ _ref5$ordersByActive,
251
+ ordersByActive,
252
+ _ordersByStatus$total,
253
+ total,
254
+ _ordersByStatus$R,
255
+ R,
256
+ _ordersByStatus$PA,
257
+ PA,
258
+ _ordersByStatus$Ex,
259
+ Ex,
260
+ _ordersByStatus$ACA,
261
+ ACA,
262
+ productsFinished,
263
+ unassigned,
264
+ inProcess,
265
+ registeredProducts,
266
+ metricsArray,
267
+ _args3 = arguments;
245
268
 
246
269
  return _regenerator.default.wrap(function _callee3$(_context3) {
247
270
  while (1) {
248
271
  switch (_context3.prev = _context3.next) {
249
272
  case 0:
273
+ isBaseline = _args3.length > 1 && _args3[1] !== undefined ? _args3[1] : false;
250
274
  isRetailer = user.is_retailer && company.retailers;
251
275
  isProvider = !user.is_retailer && user.id_role === 0;
252
276
  isCollaborator = user.id_role > 0;
253
- _context3.next = 5;
277
+ _context3.next = 6;
254
278
  return loadProductVersions(orderByStatus);
255
279
 
256
- case 5:
280
+ case 6:
257
281
  _context3.t0 = _context3.sent;
258
282
 
259
283
  if (_context3.t0) {
260
- _context3.next = 8;
284
+ _context3.next = 9;
261
285
  break;
262
286
  }
263
287
 
264
288
  _context3.t0 = {};
265
289
 
266
- case 8:
290
+ case 9:
267
291
  _ref5 = _context3.t0;
268
292
  ordersBydate = _ref5.ordersBydate;
269
- ordersByStatus = _ref5.ordersByStatus;
270
- ordersByActive = _ref5.ordersByActive;
271
- companyTrades = _ref5.companyTrades;
272
- if (trades.length === 0) setTrades(companyTrades);
293
+ _ref5$ordersByStatus = _ref5.ordersByStatus;
294
+ ordersByStatus = _ref5$ordersByStatus === void 0 ? {} : _ref5$ordersByStatus;
295
+ _ref5$ordersByActive = _ref5.ordersByActive;
296
+ ordersByActive = _ref5$ordersByActive === void 0 ? {} : _ref5$ordersByActive;
273
297
  _ordersByStatus$total = ordersByStatus.total, total = _ordersByStatus$total === void 0 ? 0 : _ordersByStatus$total, _ordersByStatus$R = ordersByStatus.R, R = _ordersByStatus$R === void 0 ? 0 : _ordersByStatus$R, _ordersByStatus$PA = ordersByStatus.PA, PA = _ordersByStatus$PA === void 0 ? 0 : _ordersByStatus$PA, _ordersByStatus$Ex = ordersByStatus.Ex, Ex = _ordersByStatus$Ex === void 0 ? 0 : _ordersByStatus$Ex, _ordersByStatus$ACA = ordersByStatus.ACA, ACA = _ordersByStatus$ACA === void 0 ? 0 : _ordersByStatus$ACA;
274
298
  productsFinished = 0;
275
299
  if (isRetailer) productsFinished = Ex;
276
- if (isProvider) productsFinished = ACA;
277
- if (isCollaborator) productsFinished = Ex + ACA;
278
- inProcess = Object.keys(ordersByStatus).reduce(function (prev, curr) {
279
- return !["total", "PA", "R", "Ex", "ACA"].includes(curr) ? prev + ordersByStatus[curr] : prev;
280
- }, 0);
281
- registeredProducts = ordersByActive.registered;
282
- metricsArray = [metricsData[0] ? metricsData[0] : {
283
- label: "Productos totales",
284
- value: total
285
- }];
286
- metricsArray.push({
287
- label: "Productos activos",
288
- value: registeredProducts
300
+ if (isProvider) productsFinished = ACA + Ex;
301
+ if (isCollaborator) productsFinished = Ex + ACA; // Lo que no está sin asignar ni terminado está en proceso, para que las
302
+ // tres tarjetas siempre sumen el total filtrado.
303
+
304
+ unassigned = PA + R;
305
+ inProcess = total - unassigned - productsFinished;
306
+ registeredProducts = (_ordersByActive$regis = ordersByActive.registered) !== null && _ordersByActive$regis !== void 0 ? _ordersByActive$regis : 0;
307
+ metricsArray = [{
308
+ label: "Catálogo activo",
309
+ value: registeredProducts,
310
+ hint: "Productos dados de alta y activos. No depende del filtro de fechas, que aplica a la fecha de la orden."
289
311
  }, {
290
- label: "Productos filtrados",
291
- value: total
312
+ label: "En órdenes de trabajo",
313
+ value: total,
314
+ hint: "Productos con orden de trabajo viva dentro de los filtros. Un producto en dos órdenes cuenta en cada una."
292
315
  }, {
293
- label: "Productos sin asignar",
294
- value: PA + R
316
+ label: "Sin asignar",
317
+ value: unassigned
295
318
  }, {
296
- label: "Productos en proceso",
319
+ label: "En proceso",
297
320
  value: inProcess
298
321
  }, {
299
- label: "Productos terminados",
322
+ label: "Terminados",
300
323
  value: productsFinished
301
- });
324
+ }];
302
325
  setMetricsData(metricsArray);
303
326
  setTotalCount(total);
327
+ if (isBaseline) setTotalUnfiltered(total);
304
328
  delete ordersByStatus.total;
305
329
  setRequiredProducts(ordersBydate);
306
330
  setProductsByStatus(ordersByStatus);
307
331
 
308
- case 28:
332
+ case 30:
309
333
  case "end":
310
334
  return _context3.stop();
311
335
  }
@@ -319,7 +343,23 @@ var Dashboard = function Dashboard(_ref) {
319
343
  }();
320
344
 
321
345
  var datesSelect = function datesSelect() {
322
- var today = new Date();
346
+ // Cada rango se calcula al momento de elegirlo y con fechas locales: si se
347
+ // parte de un Date compartido, los options se pisan entre sí, y
348
+ // new Date("2026-11-01") cae en el día anterior al oeste de UTC.
349
+ var applyRange = function applyRange(startDate) {
350
+ var endDate = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new Date();
351
+ return setDatesRange({
352
+ startDate: (0, _dashboardUtils.formatDate)(startDate),
353
+ endDate: (0, _dashboardUtils.formatDate)(endDate)
354
+ });
355
+ };
356
+
357
+ var daysAgo = function daysAgo(days) {
358
+ var date = new Date();
359
+ date.setDate(date.getDate() - days);
360
+ return date;
361
+ };
362
+
323
363
  setDateOptions([{
324
364
  id: 0,
325
365
  name: "Rango de fechas"
@@ -327,64 +367,46 @@ var Dashboard = function Dashboard(_ref) {
327
367
  id: 1,
328
368
  name: "Todo el tiempo",
329
369
  function: function _function() {
330
- return setDatesRange({
331
- startDate: (0, _dashboardUtils.formatDate)(new Date("1900-01-01")),
332
- endDate: (0, _dashboardUtils.formatDate)(new Date())
333
- });
370
+ return applyRange(new Date(1900, 0, 1));
334
371
  }
335
372
  }, {
336
373
  id: 2,
337
374
  name: "Hoy",
338
375
  function: function _function() {
339
- return setDatesRange({
340
- startDate: (0, _dashboardUtils.formatDate)(new Date()),
341
- endDate: (0, _dashboardUtils.formatDate)(new Date())
342
- });
376
+ return applyRange(new Date());
343
377
  }
344
378
  }, {
345
379
  id: 3,
346
380
  name: "Ayer",
347
381
  function: function _function() {
348
- return setDatesRange({
349
- startDate: (0, _dashboardUtils.formatDate)(new Date(today.setDate(today.getDate() - 1))),
350
- endDate: (0, _dashboardUtils.formatDate)(new Date())
351
- });
382
+ return applyRange(daysAgo(1), daysAgo(1));
352
383
  }
353
384
  }, {
354
385
  id: 4,
355
386
  name: "Esta semana",
356
387
  function: function _function() {
357
- return setDatesRange({
358
- startDate: (0, _dashboardUtils.formatDate)(new Date(today.setDate(today.getDate() - today.getDay()))),
359
- endDate: (0, _dashboardUtils.formatDate)(new Date())
360
- });
388
+ return applyRange(daysAgo(new Date().getDay()));
361
389
  }
362
390
  }, {
363
391
  id: 5,
364
392
  name: "La semana pasada",
365
393
  function: function _function() {
366
- return setDatesRange({
367
- startDate: (0, _dashboardUtils.formatDate)(new Date(today.setDate(today.getDate() - today.getDay() - 7))),
368
- endDate: (0, _dashboardUtils.formatDate)(new Date(today.setDate(today.getDate() - today.getDay() + 7)))
369
- });
394
+ var weekday = new Date().getDay();
395
+ return applyRange(daysAgo(weekday + 7), daysAgo(weekday + 1));
370
396
  }
371
397
  }, {
372
398
  id: 6,
373
399
  name: "Este mes",
374
400
  function: function _function() {
375
- return setDatesRange({
376
- startDate: (0, _dashboardUtils.formatDate)(new Date("".concat(today.getFullYear(), "-").concat(today.getMonth() + 1, "-01"))),
377
- endDate: (0, _dashboardUtils.formatDate)(today)
378
- });
401
+ var today = new Date();
402
+ return applyRange(new Date(today.getFullYear(), today.getMonth(), 1));
379
403
  }
380
404
  }, {
381
405
  id: 7,
382
406
  name: "El mes pasado",
383
407
  function: function _function() {
384
- return setDatesRange({
385
- startDate: (0, _dashboardUtils.formatDate)(new Date("".concat(today.getFullYear(), "-").concat(today.getMonth(), "-01"))),
386
- endDate: (0, _dashboardUtils.formatDate)(new Date("".concat(today.getFullYear(), "-").concat(today.getMonth() + 1, "-01")))
387
- });
408
+ var today = new Date();
409
+ return applyRange(new Date(today.getFullYear(), today.getMonth() - 1, 1), new Date(today.getFullYear(), today.getMonth(), 0));
388
410
  }
389
411
  }]);
390
412
  };
@@ -427,7 +449,7 @@ var Dashboard = function Dashboard(_ref) {
427
449
  });
428
450
  setOrderByStatus(queryObject);
429
451
  setOrderByRequired(queryObject);
430
- loadProductsByStatus(queryObject);
452
+ loadProductsByStatus(queryObject, true);
431
453
 
432
454
  case 21:
433
455
  case "end":
@@ -455,18 +477,17 @@ var Dashboard = function Dashboard(_ref) {
455
477
  if (companyId.length > 0) queryObject["companyId"] = companyId.join(",");
456
478
  if (retailerId.length > 0) queryObject["retailerId"] = retailerId.join(",");
457
479
  if (categoryId.length > 0) queryObject["categoryId"] = categoryId.join(",");
458
- if (tradesSelected.length > 0) queryObject["trades"] = encodeURIComponent(tradesSelected.join(","));
459
480
  applicantsFilter.length > 0 && (queryObject["requestedBy"] = applicantsFilter.join(","));
460
- _context5.next = 9;
461
- return loadProductsByStatus(queryObject, true);
481
+ _context5.next = 8;
482
+ return loadProductsByStatus(queryObject);
462
483
 
463
- case 9:
484
+ case 8:
464
485
  case "end":
465
486
  return _context5.stop();
466
487
  }
467
488
  }
468
489
  }, _callee5);
469
- })), [companyId, retailerId, categoryId, datesRange, applicantsFilter, tradesSelected]);
490
+ })), [companyId, retailerId, categoryId, datesRange, applicantsFilter]);
470
491
  return loading ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_Loading.Loading, {}) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_styles.Container, {
471
492
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
472
493
  className: "filters",
@@ -517,21 +538,14 @@ var Dashboard = function Dashboard(_ref) {
517
538
  parameterArray: applicantsFilter,
518
539
  setParameterArray: setApplicantsFilter,
519
540
  defaultOption: "Todos los solicitantes"
520
- }), user.id_role === 0 && (trades === null || trades === void 0 ? void 0 : trades.length) > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_CustomSelect.CustomSelect, {
521
- showSearchBar: true,
522
- options: trades,
523
- selectLabel: "Todas las marcas",
524
- placeHolder: "Buscar marca",
525
- customSelectId: "trade-select",
526
- parameterArray: tradesSelected,
527
- setParameterArray: setTradesSelected
528
541
  })]
529
542
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
530
543
  className: "metric-cards",
531
544
  children: metricsData.map(function (metric, index) {
532
545
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_MetricCard.MetricCard, {
533
546
  label: metric.label,
534
- number: metric.value
547
+ number: metric.value,
548
+ hint: metric.hint
535
549
  }, "".concat(index, "-").concat(metric.value));
536
550
  })
537
551
  }), /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
@@ -551,7 +565,7 @@ var Dashboard = function Dashboard(_ref) {
551
565
  showPercent: true,
552
566
  displayScale: false,
553
567
  totalFiltered: totalCount,
554
- total: (_metricsData$ = metricsData[0]) === null || _metricsData$ === void 0 ? void 0 : _metricsData$.value
568
+ total: totalUnfiltered
555
569
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_DashboardMetric.DashboardMetric, {
556
570
  label: "Productos solicitados",
557
571
  description: "Filtra los productos solicitados por fecha y alcance para revisar rápidamente el flujo de trabajo.",
@@ -13,6 +13,6 @@ var _styledComponents = _interopRequireDefault(require("styled-components"));
13
13
 
14
14
  var _templateObject;
15
15
 
16
- var Container = _styledComponents.default.div(_templateObject || (_templateObject = (0, _taggedTemplateLiteral2.default)(["\n padding: 30px;\n\n .filters {\n display: flex;\n & > * {\n & + * {\n margin-left: 10px;\n }\n }\n & + * {\n margin-top: 20px;\n }\n }\n\n .metric-cards {\n display: flex;\n\n > * + * {\n margin-left: 10px;\n }\n\n & + * {\n margin-top: 10px;\n }\n }\n\n .metrics-main-container {\n display: flex;\n\n > * + * {\n margin-left: 10px;\n }\n }\n"])));
16
+ var Container = _styledComponents.default.div(_templateObject || (_templateObject = (0, _taggedTemplateLiteral2.default)(["\n padding: 30px;\n\n .filters {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n\n & + * {\n margin-top: 20px;\n }\n }\n\n .metric-cards {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n\n & + * {\n margin-top: 10px;\n }\n }\n\n .metrics-main-container {\n display: grid;\n /* Dos paneles mientras quepan 320px cada uno; si no, uno debajo del otro.\n El min() evita que la columna siga midiendo 320px en pantallas menores. */\n grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));\n gap: 10px;\n\n /* Sin esto las gr\xE1ficas no pueden encogerse por debajo de su ancho. */\n > * {\n min-width: 0;\n }\n }\n\n @media (max-width: 600px) {\n padding: 15px;\n }\n"])));
17
17
 
18
18
  exports.Container = Container;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "contentoh-components-library",
3
- "version": "21.6.62",
3
+ "version": "21.6.64",
4
4
  "dependencies": {
5
5
  "@aws-amplify/auth": "^4.5.3",
6
6
  "@aws-amplify/datastore": "^3.11.0",
@@ -1,8 +1,8 @@
1
1
  import { Container } from "./styles";
2
2
 
3
- export const MetricCard = ({ label, number }) => {
3
+ export const MetricCard = ({ label, number, hint }) => {
4
4
  return (
5
- <Container>
5
+ <Container title={hint}>
6
6
  <p>{label}</p>
7
7
  <p>{number}</p>
8
8
  </Container>
@@ -26,3 +26,14 @@ DashboardMetricDefault.args = {
26
26
  RP: 49,
27
27
  },
28
28
  };
29
+
30
+ export const DashboardMetricDoughnut = Template.bind({});
31
+ DashboardMetricDoughnut.args = {
32
+ ...DashboardMetricDefault.args,
33
+ type: "doughnut",
34
+ displayLegend: true,
35
+ showPercent: true,
36
+ displayScale: false,
37
+ total: 1610,
38
+ totalFiltered: 1610,
39
+ };
@@ -1,3 +1,4 @@
1
+ import { useEffect, useRef, useState } from "react";
1
2
  import { Container } from "./styles";
2
3
  import { Graphic } from "../../atoms/Graphic";
3
4
  import {
@@ -20,6 +21,21 @@ export const DashboardMetric = ({
20
21
  total,
21
22
  totalFiltered,
22
23
  }) => {
24
+ // Chart.js coloca la leyenda por opción, no por CSS. Se mide el panel y no
25
+ // la ventana, porque el ancho real depende también del menú de la app: a
26
+ // 828px de ventana el panel queda en 331 y la leyenda de lado sale cortada.
27
+ const containerRef = useRef(null);
28
+ const [isNarrow, setIsNarrow] = useState(false);
29
+ useEffect(() => {
30
+ const node = containerRef.current;
31
+ if (!node || typeof ResizeObserver === "undefined") return undefined;
32
+ const observer = new ResizeObserver(([entry]) =>
33
+ setIsNarrow(entry.contentRect.width < 420)
34
+ );
35
+ observer.observe(node);
36
+ return () => observer.disconnect();
37
+ }, []);
38
+
23
39
  const labels =
24
40
  type === "doughnut"
25
41
  ? Object.entries(dataObject).map(
@@ -75,7 +91,7 @@ export const DashboardMetric = ({
75
91
  },
76
92
  },
77
93
  legend: {
78
- position: "right",
94
+ position: isNarrow ? "bottom" : "right",
79
95
  display: displayLegend,
80
96
  labels: {
81
97
  usePointStyle: true,
@@ -101,7 +117,7 @@ export const DashboardMetric = ({
101
117
  };
102
118
 
103
119
  return (
104
- <Container>
120
+ <Container ref={containerRef} className={isNarrow ? "is-narrow" : ""}>
105
121
  <div className="text-container">
106
122
  <p>{label}</p>
107
123
  <p>{description}</p>
@@ -7,6 +7,19 @@ export const Container = styled.div`
7
7
  padding: 24px;
8
8
  position: relative;
9
9
 
10
+ @media (max-width: 600px) {
11
+ padding: 12px;
12
+
13
+ .graphic-cotainer {
14
+ padding: 5px;
15
+ }
16
+
17
+ /* Va rotada y fuera del recuadro; con el padding chico se encima. */
18
+ > p.y-label {
19
+ display: none;
20
+ }
21
+ }
22
+
10
23
  .text-container {
11
24
  p {
12
25
  font-family: ${FontFamily.Raleway};
@@ -48,6 +61,12 @@ export const Container = styled.div`
48
61
  }
49
62
  }
50
63
 
64
+ /* Con la leyenda abajo, Chart.js le da un máximo de la altura del recuadro
65
+ y descarta las entradas que no caben; por eso crece junto con ella. */
66
+ &.is-narrow .graphic-cotainer {
67
+ height: 500px;
68
+ }
69
+
51
70
  .graphic-cotainer {
52
71
  height: 350px;
53
72
  border: 1px solid ${GlobalColors.s3};
@@ -52,9 +52,9 @@ export const getCategories = async (jwt, user, company) => {
52
52
  export const formatDate = (date) => {
53
53
  const newDate = new Date(date);
54
54
  const year = newDate.getFullYear();
55
- const month = newDate.getMonth() + 1;
56
- const day = newDate.getDate();
57
- return `${year}/${month}/${day}`;
55
+ const month = `${newDate.getMonth() + 1}`.padStart(2, "0");
56
+ const day = `${newDate.getDate()}`.padStart(2, "0");
57
+ return `${year}-${month}-${day}`;
58
58
  };
59
59
 
60
60
  const getCategoriesRecursive = (object) => {
@@ -39,9 +39,9 @@ export const Dashboard = ({ jwt, user, company }) => {
39
39
  ];
40
40
  const [applicantsFilter, setApplicantsFilter] = useState([]);
41
41
  const [totalCount, setTotalCount] = useState(0);
42
+ // Total sin filtros, solo para el porcentaje de la dona.
43
+ const [totalUnfiltered, setTotalUnfiltered] = useState(0);
42
44
  const [isProviderFinanced] = useState(company?.financedRetailers?.length > 0);
43
- const [trades, setTrades] = useState([]);
44
- const [tradesSelected, setTradesSelected] = useState([]);
45
45
 
46
46
  const loadProductVersions = async (queryObject) => {
47
47
  const keys = Object.keys(queryObject);
@@ -82,127 +82,102 @@ export const Dashboard = ({ jwt, user, company }) => {
82
82
  setRetailers(retailers);
83
83
  };
84
84
 
85
- const loadProductsByStatus = async (orderByStatus) => {
85
+ const loadProductsByStatus = async (orderByStatus, isBaseline = false) => {
86
86
  const isRetailer = user.is_retailer && company.retailers;
87
87
  const isProvider = !user.is_retailer && user.id_role === 0;
88
88
  const isCollaborator = user.id_role > 0;
89
- const { ordersBydate, ordersByStatus, ordersByActive, companyTrades } =
89
+ const { ordersBydate, ordersByStatus = {}, ordersByActive = {} } =
90
90
  (await loadProductVersions(orderByStatus)) || {};
91
91
 
92
- if (trades.length === 0) setTrades(companyTrades);
93
92
  const { total = 0, R = 0, PA = 0, Ex = 0, ACA = 0 } = ordersByStatus;
94
93
  let productsFinished = 0;
95
94
  if (isRetailer) productsFinished = Ex;
96
- if (isProvider) productsFinished = ACA;
95
+ if (isProvider) productsFinished = ACA + Ex;
97
96
  if (isCollaborator) productsFinished = Ex + ACA;
98
97
 
99
- const inProcess = Object.keys(ordersByStatus).reduce(
100
- (prev, curr) =>
101
- !["total", "PA", "R", "Ex", "ACA"].includes(curr)
102
- ? prev + ordersByStatus[curr]
103
- : prev,
104
- 0
105
- );
98
+ // Lo que no está sin asignar ni terminado está en proceso, para que las
99
+ // tres tarjetas siempre sumen el total filtrado.
100
+ const unassigned = PA + R;
101
+ const inProcess = total - unassigned - productsFinished;
106
102
 
107
- const registeredProducts = ordersByActive.registered;
108
- let metricsArray = [
109
- metricsData[0]
110
- ? metricsData[0]
111
- : { label: "Productos totales", value: total },
103
+ const registeredProducts = ordersByActive.registered ?? 0;
104
+ const metricsArray = [
105
+ {
106
+ label: "Catálogo activo",
107
+ value: registeredProducts,
108
+ hint: "Productos dados de alta y activos. No depende del filtro de fechas, que aplica a la fecha de la orden.",
109
+ },
110
+ {
111
+ label: "En órdenes de trabajo",
112
+ value: total,
113
+ hint: "Productos con orden de trabajo viva dentro de los filtros. Un producto en dos órdenes cuenta en cada una.",
114
+ },
115
+ { label: "Sin asignar", value: unassigned },
116
+ { label: "En proceso", value: inProcess },
117
+ { label: "Terminados", value: productsFinished },
112
118
  ];
113
-
114
- metricsArray.push(
115
- { label: "Productos activos", value: registeredProducts },
116
- { label: "Productos filtrados", value: total },
117
- { label: "Productos sin asignar", value: PA + R },
118
- { label: "Productos en proceso", value: inProcess },
119
- { label: "Productos terminados", value: productsFinished }
120
- );
121
119
  setMetricsData(metricsArray);
122
120
  setTotalCount(total);
121
+ if (isBaseline) setTotalUnfiltered(total);
123
122
  delete ordersByStatus.total;
124
123
  setRequiredProducts(ordersBydate);
125
124
  setProductsByStatus(ordersByStatus);
126
125
  };
127
126
 
128
127
  const datesSelect = () => {
129
- const today = new Date();
128
+ // Cada rango se calcula al momento de elegirlo y con fechas locales: si se
129
+ // parte de un Date compartido, los options se pisan entre sí, y
130
+ // new Date("2026-11-01") cae en el día anterior al oeste de UTC.
131
+ const applyRange = (startDate, endDate = new Date()) =>
132
+ setDatesRange({
133
+ startDate: formatDate(startDate),
134
+ endDate: formatDate(endDate),
135
+ });
136
+ const daysAgo = (days) => {
137
+ const date = new Date();
138
+ date.setDate(date.getDate() - days);
139
+ return date;
140
+ };
130
141
  setDateOptions([
131
142
  { id: 0, name: "Rango de fechas" },
132
143
  {
133
144
  id: 1,
134
145
  name: "Todo el tiempo",
135
- function: () =>
136
- setDatesRange({
137
- startDate: formatDate(new Date("1900-01-01")),
138
- endDate: formatDate(new Date()),
139
- }),
140
- },
141
- {
142
- id: 2,
143
- name: "Hoy",
144
- function: () =>
145
- setDatesRange({
146
- startDate: formatDate(new Date()),
147
- endDate: formatDate(new Date()),
148
- }),
149
- },
150
- {
151
- id: 3,
152
- name: "Ayer",
153
- function: () =>
154
- setDatesRange({
155
- startDate: formatDate(new Date(today.setDate(today.getDate() - 1))),
156
- endDate: formatDate(new Date()),
157
- }),
146
+ function: () => applyRange(new Date(1900, 0, 1)),
158
147
  },
148
+ { id: 2, name: "Hoy", function: () => applyRange(new Date()) },
149
+ { id: 3, name: "Ayer", function: () => applyRange(daysAgo(1), daysAgo(1)) },
159
150
  {
160
151
  id: 4,
161
152
  name: "Esta semana",
162
- function: () =>
163
- setDatesRange({
164
- startDate: formatDate(
165
- new Date(today.setDate(today.getDate() - today.getDay()))
166
- ),
167
- endDate: formatDate(new Date()),
168
- }),
153
+ function: () => applyRange(daysAgo(new Date().getDay())),
169
154
  },
170
155
  {
171
156
  id: 5,
172
157
  name: "La semana pasada",
173
- function: () =>
174
- setDatesRange({
175
- startDate: formatDate(
176
- new Date(today.setDate(today.getDate() - today.getDay() - 7))
177
- ),
178
- endDate: formatDate(
179
- new Date(today.setDate(today.getDate() - today.getDay() + 7))
180
- ),
181
- }),
158
+ function: () => {
159
+ const weekday = new Date().getDay();
160
+ return applyRange(daysAgo(weekday + 7), daysAgo(weekday + 1));
161
+ },
182
162
  },
183
163
  {
184
164
  id: 6,
185
165
  name: "Este mes",
186
- function: () =>
187
- setDatesRange({
188
- startDate: formatDate(
189
- new Date(`${today.getFullYear()}-${today.getMonth() + 1}-01`)
190
- ),
191
- endDate: formatDate(today),
192
- }),
166
+ function: () => {
167
+ const today = new Date();
168
+ return applyRange(new Date(today.getFullYear(), today.getMonth(), 1));
169
+ },
193
170
  },
194
171
  {
195
172
  id: 7,
196
173
  name: "El mes pasado",
197
- function: () =>
198
- setDatesRange({
199
- startDate: formatDate(
200
- new Date(`${today.getFullYear()}-${today.getMonth()}-01`)
201
- ),
202
- endDate: formatDate(
203
- new Date(`${today.getFullYear()}-${today.getMonth() + 1}-01`)
204
- ),
205
- }),
174
+ function: () => {
175
+ const today = new Date();
176
+ return applyRange(
177
+ new Date(today.getFullYear(), today.getMonth() - 1, 1),
178
+ new Date(today.getFullYear(), today.getMonth(), 0)
179
+ );
180
+ },
206
181
  },
207
182
  ]);
208
183
  };
@@ -219,7 +194,7 @@ export const Dashboard = ({ jwt, user, company }) => {
219
194
  setDatesRange({ startDate, endDate });
220
195
  setOrderByStatus(queryObject);
221
196
  setOrderByRequired(queryObject);
222
- loadProductsByStatus(queryObject);
197
+ loadProductsByStatus(queryObject, true);
223
198
  }, []);
224
199
 
225
200
  useEffect(() => {
@@ -241,20 +216,11 @@ export const Dashboard = ({ jwt, user, company }) => {
241
216
  if (companyId.length > 0) queryObject["companyId"] = companyId.join(",");
242
217
  if (retailerId.length > 0) queryObject["retailerId"] = retailerId.join(",");
243
218
  if (categoryId.length > 0) queryObject["categoryId"] = categoryId.join(",");
244
- if (tradesSelected.length > 0)
245
- queryObject["trades"] = encodeURIComponent(tradesSelected.join(","));
246
219
  applicantsFilter.length > 0 &&
247
220
  (queryObject["requestedBy"] = applicantsFilter.join(","));
248
221
 
249
- await loadProductsByStatus(queryObject, true);
250
- }, [
251
- companyId,
252
- retailerId,
253
- categoryId,
254
- datesRange,
255
- applicantsFilter,
256
- tradesSelected,
257
- ]);
222
+ await loadProductsByStatus(queryObject);
223
+ }, [companyId, retailerId, categoryId, datesRange, applicantsFilter]);
258
224
 
259
225
  return loading ? (
260
226
  <Loading />
@@ -319,17 +285,6 @@ export const Dashboard = ({ jwt, user, company }) => {
319
285
  defaultOption="Todos los solicitantes"
320
286
  />
321
287
  )}
322
- {user.id_role === 0 && trades?.length > 0 && (
323
- <CustomSelect
324
- showSearchBar={true}
325
- options={trades}
326
- selectLabel="Todas las marcas"
327
- placeHolder="Buscar marca"
328
- customSelectId="trade-select"
329
- parameterArray={tradesSelected}
330
- setParameterArray={setTradesSelected}
331
- />
332
- )}
333
288
  </div>
334
289
  <div className="metric-cards">
335
290
  {metricsData.map((metric, index) => (
@@ -337,6 +292,7 @@ export const Dashboard = ({ jwt, user, company }) => {
337
292
  key={`${index}-${metric.value}`}
338
293
  label={metric.label}
339
294
  number={metric.value}
295
+ hint={metric.hint}
340
296
  />
341
297
  ))}
342
298
  </div>
@@ -358,7 +314,7 @@ export const Dashboard = ({ jwt, user, company }) => {
358
314
  showPercent={true}
359
315
  displayScale={false}
360
316
  totalFiltered={totalCount}
361
- total={metricsData[0]?.value}
317
+ total={totalUnfiltered}
362
318
  />
363
319
  <DashboardMetric
364
320
  label={"Productos solicitados"}
@@ -5,11 +5,9 @@ export const Container = styled.div`
5
5
 
6
6
  .filters {
7
7
  display: flex;
8
- & > * {
9
- & + * {
10
- margin-left: 10px;
11
- }
12
- }
8
+ flex-wrap: wrap;
9
+ gap: 10px;
10
+
13
11
  & + * {
14
12
  margin-top: 20px;
15
13
  }
@@ -17,10 +15,8 @@ export const Container = styled.div`
17
15
 
18
16
  .metric-cards {
19
17
  display: flex;
20
-
21
- > * + * {
22
- margin-left: 10px;
23
- }
18
+ flex-wrap: wrap;
19
+ gap: 10px;
24
20
 
25
21
  & + * {
26
22
  margin-top: 10px;
@@ -28,10 +24,19 @@ export const Container = styled.div`
28
24
  }
29
25
 
30
26
  .metrics-main-container {
31
- display: flex;
27
+ display: grid;
28
+ /* Dos paneles mientras quepan 320px cada uno; si no, uno debajo del otro.
29
+ El min() evita que la columna siga midiendo 320px en pantallas menores. */
30
+ grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
31
+ gap: 10px;
32
32
 
33
- > * + * {
34
- margin-left: 10px;
33
+ /* Sin esto las gráficas no pueden encogerse por debajo de su ancho. */
34
+ > * {
35
+ min-width: 0;
35
36
  }
36
37
  }
38
+
39
+ @media (max-width: 600px) {
40
+ padding: 15px;
41
+ }
37
42
  `;