ads-nuxt 0.1.1 → 0.1.3

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 (74) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +577 -546
  3. package/app/app.config.ts +25 -25
  4. package/app/assets/ads/checkbox-check.svg +5 -5
  5. package/app/assets/ads/checkbox-minus.svg +5 -5
  6. package/app/assets/brand/farmarcas.svg +6 -6
  7. package/app/assets/brand/radar.svg +9 -9
  8. package/app/assets/css/component-tokens.css +403 -403
  9. package/app/assets/css/main.css +617 -617
  10. package/app/components/AdsBadge.vue +48 -48
  11. package/app/components/AdsBroadcast.vue +45 -45
  12. package/app/components/AdsButton.vue +569 -569
  13. package/app/components/AdsButtonGroup.vue +39 -39
  14. package/app/components/AdsButtonMenu.vue +48 -48
  15. package/app/components/AdsCard.vue +117 -117
  16. package/app/components/AdsCharttip.vue +78 -78
  17. package/app/components/AdsCheckbox.vue +77 -77
  18. package/app/components/AdsDatePicker.vue +473 -473
  19. package/app/components/AdsDropdownMenu.vue +19 -19
  20. package/app/components/AdsEmptyState.vue +58 -58
  21. package/app/components/AdsFarmarcasPill.vue +74 -74
  22. package/app/components/AdsFeatureCard.vue +217 -217
  23. package/app/components/AdsFileCard.vue +101 -101
  24. package/app/components/AdsFilterDropdown.vue +174 -174
  25. package/app/components/AdsFilterOption.vue +72 -72
  26. package/app/components/AdsFilterSelect.vue +202 -202
  27. package/app/components/AdsFooter.vue +25 -25
  28. package/app/components/AdsInput.vue +143 -143
  29. package/app/components/AdsListDropdown.vue +44 -44
  30. package/app/components/AdsLoading.vue +109 -109
  31. package/app/components/AdsMenuBar.vue +103 -103
  32. package/app/components/AdsMetricCard.vue +119 -119
  33. package/app/components/AdsMiniPill.vue +40 -40
  34. package/app/components/AdsModal.vue +157 -157
  35. package/app/components/AdsMonthPicker.vue +80 -80
  36. package/app/components/AdsNavbarApp.vue +67 -67
  37. package/app/components/AdsNavbarDesk.vue +62 -62
  38. package/app/components/AdsNotabilityPill.vue +27 -27
  39. package/app/components/AdsNumberInput.vue +166 -166
  40. package/app/components/AdsPagination.vue +91 -91
  41. package/app/components/AdsProductTag.vue +61 -61
  42. package/app/components/AdsRadio.vue +70 -70
  43. package/app/components/AdsSearchInput.vue +113 -113
  44. package/app/components/AdsSegmentedTabs.vue +28 -28
  45. package/app/components/AdsSideMenu.vue +101 -101
  46. package/app/components/AdsSmalltip.vue +44 -44
  47. package/app/components/AdsSteptip.vue +101 -101
  48. package/app/components/AdsSwitch.vue +59 -59
  49. package/app/components/AdsTable.vue +100 -100
  50. package/app/components/AdsTableToolbar.vue +44 -44
  51. package/app/components/AdsTabs.vue +51 -51
  52. package/app/components/AdsTextarea.vue +109 -109
  53. package/app/components/AdsToast.vue +181 -181
  54. package/app/components/AdsTooltip.vue +66 -66
  55. package/app/components/InvertedTooltip.vue +40 -40
  56. package/app/components/OverlayLoading.vue +173 -173
  57. package/app/components/ProgressBarLoading.vue +256 -256
  58. package/app/composables/useOverlayLoading.ts +66 -66
  59. package/app/locale/pt-br.ts +6 -6
  60. package/app/plugins/ads-toast.client.ts +24 -24
  61. package/app/theme/ads-theme.ts +370 -370
  62. package/app/theme/component-tokens.json +402 -402
  63. package/app/theme/component-tokens.ts +815 -815
  64. package/app/types/overlay-loading.types.ts +26 -26
  65. package/app/utils/ads-filter-select.ts +172 -172
  66. package/app/utils/ads-table.ts +204 -204
  67. package/app/utils/calendar-date.ts +128 -128
  68. package/app/utils/category-role.ts +45 -45
  69. package/app/utils/money.ts +77 -77
  70. package/app/utils/notability.ts +8 -8
  71. package/app/utils/parser.ts +134 -134
  72. package/app/utils/percentage.ts +54 -54
  73. package/nuxt.config.ts +31 -32
  74. package/package.json +55 -46
@@ -1,45 +1,45 @@
1
- export const CATEGORY_ROLES = [
2
- "destination",
3
- "routine",
4
- "seasonal",
5
- "convenience",
6
- ] as const;
7
-
8
- export type CategoryRole = (typeof CATEGORY_ROLES)[number];
9
-
10
- const CATEGORY_ROLE_LABELS: Record<CategoryRole, string> = {
11
- destination: "Destino",
12
- routine: "Rotina",
13
- seasonal: "Sazonal",
14
- convenience: "Conveniência",
15
- };
16
-
17
- export const CATEGORY_ROLE_OPTIONS = CATEGORY_ROLES.map((role) => ({
18
- label: CATEGORY_ROLE_LABELS[role],
19
- value: role,
20
- }));
21
-
22
- export const formatCategoryRole = (role: CategoryRole | null): string =>
23
- role ? CATEGORY_ROLE_LABELS[role] : "-";
24
-
25
- const stripDiacritics = (value: string): string =>
26
- value.normalize("NFD").replace(/\p{Diacritic}/gu, "");
27
-
28
- export const parseCategoryRole = (
29
- raw: string | null | undefined,
30
- ): CategoryRole | null => {
31
- const normalized = stripDiacritics(String(raw ?? "").trim()).toLowerCase();
32
-
33
- if (!normalized) return null;
34
-
35
- if (normalized.startsWith("destin")) return "destination";
36
- if (normalized.startsWith("rotin") || normalized.startsWith("routin")) {
37
- return "routine";
38
- }
39
- if (normalized.startsWith("sazon") || normalized.startsWith("season")) {
40
- return "seasonal";
41
- }
42
- if (normalized.startsWith("conven")) return "convenience";
43
-
44
- return null;
45
- };
1
+ export const CATEGORY_ROLES = [
2
+ "destination",
3
+ "routine",
4
+ "seasonal",
5
+ "convenience",
6
+ ] as const;
7
+
8
+ export type CategoryRole = (typeof CATEGORY_ROLES)[number];
9
+
10
+ const CATEGORY_ROLE_LABELS: Record<CategoryRole, string> = {
11
+ destination: "Destino",
12
+ routine: "Rotina",
13
+ seasonal: "Sazonal",
14
+ convenience: "Conveniência",
15
+ };
16
+
17
+ export const CATEGORY_ROLE_OPTIONS = CATEGORY_ROLES.map((role) => ({
18
+ label: CATEGORY_ROLE_LABELS[role],
19
+ value: role,
20
+ }));
21
+
22
+ export const formatCategoryRole = (role: CategoryRole | null): string =>
23
+ role ? CATEGORY_ROLE_LABELS[role] : "-";
24
+
25
+ const stripDiacritics = (value: string): string =>
26
+ value.normalize("NFD").replace(/\p{Diacritic}/gu, "");
27
+
28
+ export const parseCategoryRole = (
29
+ raw: string | null | undefined,
30
+ ): CategoryRole | null => {
31
+ const normalized = stripDiacritics(String(raw ?? "").trim()).toLowerCase();
32
+
33
+ if (!normalized) return null;
34
+
35
+ if (normalized.startsWith("destin")) return "destination";
36
+ if (normalized.startsWith("rotin") || normalized.startsWith("routin")) {
37
+ return "routine";
38
+ }
39
+ if (normalized.startsWith("sazon") || normalized.startsWith("season")) {
40
+ return "seasonal";
41
+ }
42
+ if (normalized.startsWith("conven")) return "convenience";
43
+
44
+ return null;
45
+ };
@@ -1,77 +1,77 @@
1
- export const numberToBRL = (
2
- price: number | undefined | null,
3
- withSignal = false
4
- ): string => {
5
- if (price == null) return "-";
6
-
7
- const sign = price < 0 ? "-" : withSignal && price > 0 ? "+" : "";
8
- const value = new Intl.NumberFormat("pt-BR", {
9
- style: "currency",
10
- currency: "BRL",
11
- }).format(Math.abs(price));
12
-
13
- return `R$ ${sign}${value.replace("R$", "").trim()}`;
14
- };
15
-
16
- const MILLION = 1_000_000;
17
- const THOUSAND = 1_000;
18
-
19
- const COMPACT_DECIMAL_FORMATTER = new Intl.NumberFormat("pt-BR", {
20
- minimumFractionDigits: 2,
21
- maximumFractionDigits: 2,
22
- });
23
-
24
- export const numberToCompactBRL = (
25
- value: number | undefined | null,
26
- ): string => {
27
- if (value == null || Number.isNaN(value)) return "-";
28
-
29
- const sign = value < 0 ? "-" : "";
30
- const absolute = Math.abs(value);
31
-
32
- if (absolute >= MILLION) {
33
- return `R$ ${sign}${COMPACT_DECIMAL_FORMATTER.format(absolute / MILLION)}MM`;
34
- }
35
-
36
- if (absolute >= THOUSAND) {
37
- return `R$ ${sign}${COMPACT_DECIMAL_FORMATTER.format(absolute / THOUSAND)}M`;
38
- }
39
-
40
- return numberToBRL(value);
41
- };
42
-
43
- export const parseStringMoneyToFloat = (value: string): number | null => {
44
- if (!value || typeof value !== "string") return null;
45
- const cleanValue = value.replace(/[^\d,.-]/g, "");
46
- if (!cleanValue) return null;
47
- const normalizedValue = cleanValue.replace(",", ".");
48
- const parsed = parseFloat(normalizedValue);
49
- return Number.isNaN(parsed) ? null : parsed;
50
- };
51
-
52
- const BRL_INPUT_FORMATTER = new Intl.NumberFormat("pt-BR", {
53
- minimumFractionDigits: 2,
54
- maximumFractionDigits: 2,
55
- });
56
-
57
- /**
58
- * Formata valor numérico para exibição em input monetário (padrão R$).
59
- * pt-BR: vírgula decimal, ponto milhares, 2 decimais. Sem símbolo R$.
60
- * Use o valor retornado apenas para exibição; persista sempre o número.
61
- */
62
- export function formatPriceInput(value?: number | null): string {
63
- if (value === undefined || value === null || Number.isNaN(value)) return "";
64
- return BRL_INPUT_FORMATTER.format(Number(value.toFixed(2)));
65
- }
66
-
67
- /**
68
- * Converte o valor digitado em máscara monetária para número em reais.
69
- * Sanitiza (apenas dígitos), interpreta como centavos (últimos 2 = decimais)
70
- * e retorna valor para persistência no backend.
71
- */
72
- export function parsePriceInput(value: string | number): number {
73
- if (typeof value === "number" && !Number.isNaN(value))
74
- return Math.round(value * 100) / 100;
75
- const digits = String(value ?? "").replace(/\D/g, "");
76
- return parseInt(digits || "0", 10) / 100;
77
- }
1
+ export const numberToBRL = (
2
+ price: number | undefined | null,
3
+ withSignal = false
4
+ ): string => {
5
+ if (price == null) return "-";
6
+
7
+ const sign = price < 0 ? "-" : withSignal && price > 0 ? "+" : "";
8
+ const value = new Intl.NumberFormat("pt-BR", {
9
+ style: "currency",
10
+ currency: "BRL",
11
+ }).format(Math.abs(price));
12
+
13
+ return `R$ ${sign}${value.replace("R$", "").trim()}`;
14
+ };
15
+
16
+ const MILLION = 1_000_000;
17
+ const THOUSAND = 1_000;
18
+
19
+ const COMPACT_DECIMAL_FORMATTER = new Intl.NumberFormat("pt-BR", {
20
+ minimumFractionDigits: 2,
21
+ maximumFractionDigits: 2,
22
+ });
23
+
24
+ export const numberToCompactBRL = (
25
+ value: number | undefined | null,
26
+ ): string => {
27
+ if (value == null || Number.isNaN(value)) return "-";
28
+
29
+ const sign = value < 0 ? "-" : "";
30
+ const absolute = Math.abs(value);
31
+
32
+ if (absolute >= MILLION) {
33
+ return `R$ ${sign}${COMPACT_DECIMAL_FORMATTER.format(absolute / MILLION)}MM`;
34
+ }
35
+
36
+ if (absolute >= THOUSAND) {
37
+ return `R$ ${sign}${COMPACT_DECIMAL_FORMATTER.format(absolute / THOUSAND)}M`;
38
+ }
39
+
40
+ return numberToBRL(value);
41
+ };
42
+
43
+ export const parseStringMoneyToFloat = (value: string): number | null => {
44
+ if (!value || typeof value !== "string") return null;
45
+ const cleanValue = value.replace(/[^\d,.-]/g, "");
46
+ if (!cleanValue) return null;
47
+ const normalizedValue = cleanValue.replace(",", ".");
48
+ const parsed = parseFloat(normalizedValue);
49
+ return Number.isNaN(parsed) ? null : parsed;
50
+ };
51
+
52
+ const BRL_INPUT_FORMATTER = new Intl.NumberFormat("pt-BR", {
53
+ minimumFractionDigits: 2,
54
+ maximumFractionDigits: 2,
55
+ });
56
+
57
+ /**
58
+ * Formata valor numérico para exibição em input monetário (padrão R$).
59
+ * pt-BR: vírgula decimal, ponto milhares, 2 decimais. Sem símbolo R$.
60
+ * Use o valor retornado apenas para exibição; persista sempre o número.
61
+ */
62
+ export function formatPriceInput(value?: number | null): string {
63
+ if (value === undefined || value === null || Number.isNaN(value)) return "";
64
+ return BRL_INPUT_FORMATTER.format(Number(value.toFixed(2)));
65
+ }
66
+
67
+ /**
68
+ * Converte o valor digitado em máscara monetária para número em reais.
69
+ * Sanitiza (apenas dígitos), interpreta como centavos (últimos 2 = decimais)
70
+ * e retorna valor para persistência no backend.
71
+ */
72
+ export function parsePriceInput(value: string | number): number {
73
+ if (typeof value === "number" && !Number.isNaN(value))
74
+ return Math.round(value * 100) / 100;
75
+ const digits = String(value ?? "").replace(/\D/g, "");
76
+ return parseInt(digits || "0", 10) / 100;
77
+ }
@@ -1,8 +1,8 @@
1
- const NOTABILITY_LABELS: Record<string, string> = {
2
- traffic: "Gerador de Tráfego",
3
- image: "Formador de Imagem",
4
- profit: "Gerador de Lucro",
5
- };
6
-
7
- export const formatNotability = (notability: string): string =>
8
- NOTABILITY_LABELS[notability] ?? notability;
1
+ const NOTABILITY_LABELS: Record<string, string> = {
2
+ traffic: "Gerador de Tráfego",
3
+ image: "Formador de Imagem",
4
+ profit: "Gerador de Lucro",
5
+ };
6
+
7
+ export const formatNotability = (notability: string): string =>
8
+ NOTABILITY_LABELS[notability] ?? notability;
@@ -1,134 +1,134 @@
1
- export const parseToPercentage = (value: number) =>
2
- Number((value * 100).toFixed(2));
3
-
4
- export const parsePercentageToFloat = (value: string | number) => {
5
- if (typeof value === "string") {
6
- value = value.replace(",", ".");
7
- return Number(value) / 100;
8
- }
9
- return value / 100;
10
- };
11
-
12
- export const parseFloatToDecimal = (value: number) => {
13
- return value.toLocaleString("pt-BR", {
14
- minimumFractionDigits: 2,
15
- maximumFractionDigits: 2,
16
- });
17
- };
18
-
19
- const DATE_ONLY_REGEX = /^\d{4}-\d{2}-\d{2}$/;
20
-
21
- const BUSINESS_CALENDAR_TIMEZONE = "America/Sao_Paulo";
22
-
23
- const getCalendarPart = (
24
- parts: Intl.DateTimeFormatPart[],
25
- type: Intl.DateTimeFormatPartTypes,
26
- ): string | undefined => parts.find((part) => part.type === type)?.value;
27
-
28
- export function getTodayDateString(now: Date = new Date()): string {
29
- const parts = new Intl.DateTimeFormat("en-CA", {
30
- timeZone: BUSINESS_CALENDAR_TIMEZONE,
31
- year: "numeric",
32
- month: "2-digit",
33
- day: "2-digit",
34
- }).formatToParts(now);
35
-
36
- const year = getCalendarPart(parts, "year");
37
- const month = getCalendarPart(parts, "month");
38
- const day = getCalendarPart(parts, "day");
39
-
40
- if (!year || !month || !day) {
41
- throw new Error(
42
- "Não foi possível obter a data civil em America/Sao_Paulo",
43
- );
44
- }
45
-
46
- return `${year}-${month}-${day}`;
47
- }
48
-
49
- export function toDateOnlyString(dateStr: string): string {
50
- return dateStr.includes("T") ? dateStr.slice(0, 10) : dateStr;
51
- }
52
-
53
- export const extractDateOnly = (value: string | Date): string => {
54
- if (typeof value === "string") {
55
- if (DATE_ONLY_REGEX.test(value)) return value;
56
- const iso = value.trim();
57
- if (iso.length >= 10) return iso.slice(0, 10);
58
- return "";
59
- }
60
- const iso = value.toISOString();
61
- return iso.slice(0, 10);
62
- };
63
-
64
-
65
- export const formatDateOnlyToPtBr = (value: string | Date): string => {
66
- const yyyyMmDd = extractDateOnly(value);
67
- if (!yyyyMmDd || !DATE_ONLY_REGEX.test(yyyyMmDd)) return "";
68
-
69
- const [y, m, d] = yyyyMmDd.split("-").map(Number);
70
- const day = String(d).padStart(2, "0");
71
- const month = String(m).padStart(2, "0");
72
- const year = String(y);
73
-
74
- return `${day}/${month}/${year}`;
75
- };
76
-
77
- export const daysBetweenDateOnly = (start: string, end: string): number => {
78
- const startStr = extractDateOnly(start);
79
- const endStr = extractDateOnly(end);
80
- if (!startStr || !endStr) return 0;
81
-
82
- const startDate = new Date(startStr + "T12:00:00");
83
- const endDate = new Date(endStr + "T12:00:00");
84
- const diffMs = endDate.getTime() - startDate.getTime();
85
- return Math.round(diffMs / (1000 * 60 * 60 * 24));
86
- };
87
-
88
- export const daysFromTodayToDateOnly = (isoOrDateStr: string): number => {
89
- const endStr = extractDateOnly(isoOrDateStr);
90
- if (!endStr) return 0;
91
-
92
- const todayStr = getTodayDateString();
93
- const todayNoon = new Date(todayStr + "T12:00:00");
94
- const endNoon = new Date(endStr + "T12:00:00");
95
- const diffMs = endNoon.getTime() - todayNoon.getTime();
96
- return Math.round(diffMs / (1000 * 60 * 60 * 24));
97
- };
98
-
99
- export const formatDateToPtBr = (date: Date | string, onlyDate = false): string => {
100
- const d = new Date(date);
101
- if (isNaN(d.getTime())) return "";
102
-
103
- if (onlyDate) {
104
- return d.toLocaleDateString("pt-BR");
105
- }
106
-
107
- return d.toLocaleDateString("pt-BR") + " " + d.toLocaleTimeString("pt-BR", {
108
- hour: "2-digit",
109
- minute: "2-digit"
110
- });
111
- };
112
-
113
- export const normalizePercentage = (
114
- value: string | number | undefined,
115
- fallback?: number
116
- ): number => {
117
- if (value === undefined || value === null || value === "") {
118
- return fallback ?? 0;
119
- }
120
-
121
- if (typeof value === "string") {
122
- const numericValue = Number(value.replace(",", "."));
123
- if (Number.isNaN(numericValue)) {
124
- return fallback ?? 0;
125
- }
126
- return parsePercentageToFloat(numericValue);
127
- }
128
-
129
- if (typeof value === "number") {
130
- return parsePercentageToFloat(value);
131
- }
132
-
133
- return fallback ?? 0;
134
- };
1
+ export const parseToPercentage = (value: number) =>
2
+ Number((value * 100).toFixed(2));
3
+
4
+ export const parsePercentageToFloat = (value: string | number) => {
5
+ if (typeof value === "string") {
6
+ value = value.replace(",", ".");
7
+ return Number(value) / 100;
8
+ }
9
+ return value / 100;
10
+ };
11
+
12
+ export const parseFloatToDecimal = (value: number) => {
13
+ return value.toLocaleString("pt-BR", {
14
+ minimumFractionDigits: 2,
15
+ maximumFractionDigits: 2,
16
+ });
17
+ };
18
+
19
+ const DATE_ONLY_REGEX = /^\d{4}-\d{2}-\d{2}$/;
20
+
21
+ const BUSINESS_CALENDAR_TIMEZONE = "America/Sao_Paulo";
22
+
23
+ const getCalendarPart = (
24
+ parts: Intl.DateTimeFormatPart[],
25
+ type: Intl.DateTimeFormatPartTypes,
26
+ ): string | undefined => parts.find((part) => part.type === type)?.value;
27
+
28
+ export function getTodayDateString(now: Date = new Date()): string {
29
+ const parts = new Intl.DateTimeFormat("en-CA", {
30
+ timeZone: BUSINESS_CALENDAR_TIMEZONE,
31
+ year: "numeric",
32
+ month: "2-digit",
33
+ day: "2-digit",
34
+ }).formatToParts(now);
35
+
36
+ const year = getCalendarPart(parts, "year");
37
+ const month = getCalendarPart(parts, "month");
38
+ const day = getCalendarPart(parts, "day");
39
+
40
+ if (!year || !month || !day) {
41
+ throw new Error(
42
+ "Não foi possível obter a data civil em America/Sao_Paulo",
43
+ );
44
+ }
45
+
46
+ return `${year}-${month}-${day}`;
47
+ }
48
+
49
+ export function toDateOnlyString(dateStr: string): string {
50
+ return dateStr.includes("T") ? dateStr.slice(0, 10) : dateStr;
51
+ }
52
+
53
+ export const extractDateOnly = (value: string | Date): string => {
54
+ if (typeof value === "string") {
55
+ if (DATE_ONLY_REGEX.test(value)) return value;
56
+ const iso = value.trim();
57
+ if (iso.length >= 10) return iso.slice(0, 10);
58
+ return "";
59
+ }
60
+ const iso = value.toISOString();
61
+ return iso.slice(0, 10);
62
+ };
63
+
64
+
65
+ export const formatDateOnlyToPtBr = (value: string | Date): string => {
66
+ const yyyyMmDd = extractDateOnly(value);
67
+ if (!yyyyMmDd || !DATE_ONLY_REGEX.test(yyyyMmDd)) return "";
68
+
69
+ const [y, m, d] = yyyyMmDd.split("-").map(Number);
70
+ const day = String(d).padStart(2, "0");
71
+ const month = String(m).padStart(2, "0");
72
+ const year = String(y);
73
+
74
+ return `${day}/${month}/${year}`;
75
+ };
76
+
77
+ export const daysBetweenDateOnly = (start: string, end: string): number => {
78
+ const startStr = extractDateOnly(start);
79
+ const endStr = extractDateOnly(end);
80
+ if (!startStr || !endStr) return 0;
81
+
82
+ const startDate = new Date(startStr + "T12:00:00");
83
+ const endDate = new Date(endStr + "T12:00:00");
84
+ const diffMs = endDate.getTime() - startDate.getTime();
85
+ return Math.round(diffMs / (1000 * 60 * 60 * 24));
86
+ };
87
+
88
+ export const daysFromTodayToDateOnly = (isoOrDateStr: string): number => {
89
+ const endStr = extractDateOnly(isoOrDateStr);
90
+ if (!endStr) return 0;
91
+
92
+ const todayStr = getTodayDateString();
93
+ const todayNoon = new Date(todayStr + "T12:00:00");
94
+ const endNoon = new Date(endStr + "T12:00:00");
95
+ const diffMs = endNoon.getTime() - todayNoon.getTime();
96
+ return Math.round(diffMs / (1000 * 60 * 60 * 24));
97
+ };
98
+
99
+ export const formatDateToPtBr = (date: Date | string, onlyDate = false): string => {
100
+ const d = new Date(date);
101
+ if (isNaN(d.getTime())) return "";
102
+
103
+ if (onlyDate) {
104
+ return d.toLocaleDateString("pt-BR");
105
+ }
106
+
107
+ return d.toLocaleDateString("pt-BR") + " " + d.toLocaleTimeString("pt-BR", {
108
+ hour: "2-digit",
109
+ minute: "2-digit"
110
+ });
111
+ };
112
+
113
+ export const normalizePercentage = (
114
+ value: string | number | undefined,
115
+ fallback?: number
116
+ ): number => {
117
+ if (value === undefined || value === null || value === "") {
118
+ return fallback ?? 0;
119
+ }
120
+
121
+ if (typeof value === "string") {
122
+ const numericValue = Number(value.replace(",", "."));
123
+ if (Number.isNaN(numericValue)) {
124
+ return fallback ?? 0;
125
+ }
126
+ return parsePercentageToFloat(numericValue);
127
+ }
128
+
129
+ if (typeof value === "number") {
130
+ return parsePercentageToFloat(value);
131
+ }
132
+
133
+ return fallback ?? 0;
134
+ };
@@ -1,54 +1,54 @@
1
- /**
2
- * Formata um número como porcentagem para exibição.
3
- * Exibe casas decimais apenas quando o valor não for inteiro.
4
- *
5
- * @param value - Valor numérico da porcentagem (ex: 3.25, 50, 100)
6
- * @returns String formatada com o símbolo % (ex: "3.25%", "50%", "100%")
7
- *
8
- * @example
9
- * formatPercentage(3.25) // "3.25%"
10
- * formatPercentage(50) // "50%"
11
- * formatPercentage(100) // "100%"
12
- * formatPercentage(0) // "0%"
13
- */
14
- export const formatPercentage = (value: number): string => {
15
- // Se for inteiro, não mostra casas decimais
16
- if (Number.isInteger(value)) {
17
- return `${value}%`;
18
- }
19
-
20
- // Caso contrário, mostra 2 casas decimais
21
- return `${value.toFixed(2)}%`;
22
- };
23
-
24
- /**
25
- * Formata um número como porcentagem com um número específico de casas decimais.
26
- *
27
- * @param value - Valor numérico da porcentagem
28
- * @param decimals - Número de casas decimais (padrão: 2)
29
- * @returns String formatada com o símbolo %
30
- *
31
- * @example
32
- * formatPercentageWithDecimals(3.256, 2) // "3.26%"
33
- * formatPercentageWithDecimals(50, 1) // "50.0%"
34
- */
35
- export const formatPercentageWithDecimals = (
36
- value: number,
37
- decimals: number = 2
38
- ): string => {
39
- return `${value.toFixed(decimals)}%`;
40
- };
41
-
42
- export const formatPercentagePtBr = (
43
- value: number | undefined | null,
44
- decimals: number = 1
45
- ): string => {
46
- if (value == null || Number.isNaN(value)) return "-";
47
-
48
- const formatted = new Intl.NumberFormat("pt-BR", {
49
- minimumFractionDigits: decimals,
50
- maximumFractionDigits: decimals,
51
- }).format(value);
52
-
53
- return `${formatted}%`;
54
- };
1
+ /**
2
+ * Formata um número como porcentagem para exibição.
3
+ * Exibe casas decimais apenas quando o valor não for inteiro.
4
+ *
5
+ * @param value - Valor numérico da porcentagem (ex: 3.25, 50, 100)
6
+ * @returns String formatada com o símbolo % (ex: "3.25%", "50%", "100%")
7
+ *
8
+ * @example
9
+ * formatPercentage(3.25) // "3.25%"
10
+ * formatPercentage(50) // "50%"
11
+ * formatPercentage(100) // "100%"
12
+ * formatPercentage(0) // "0%"
13
+ */
14
+ export const formatPercentage = (value: number): string => {
15
+ // Se for inteiro, não mostra casas decimais
16
+ if (Number.isInteger(value)) {
17
+ return `${value}%`;
18
+ }
19
+
20
+ // Caso contrário, mostra 2 casas decimais
21
+ return `${value.toFixed(2)}%`;
22
+ };
23
+
24
+ /**
25
+ * Formata um número como porcentagem com um número específico de casas decimais.
26
+ *
27
+ * @param value - Valor numérico da porcentagem
28
+ * @param decimals - Número de casas decimais (padrão: 2)
29
+ * @returns String formatada com o símbolo %
30
+ *
31
+ * @example
32
+ * formatPercentageWithDecimals(3.256, 2) // "3.26%"
33
+ * formatPercentageWithDecimals(50, 1) // "50.0%"
34
+ */
35
+ export const formatPercentageWithDecimals = (
36
+ value: number,
37
+ decimals: number = 2
38
+ ): string => {
39
+ return `${value.toFixed(decimals)}%`;
40
+ };
41
+
42
+ export const formatPercentagePtBr = (
43
+ value: number | undefined | null,
44
+ decimals: number = 1
45
+ ): string => {
46
+ if (value == null || Number.isNaN(value)) return "-";
47
+
48
+ const formatted = new Intl.NumberFormat("pt-BR", {
49
+ minimumFractionDigits: decimals,
50
+ maximumFractionDigits: decimals,
51
+ }).format(value);
52
+
53
+ return `${formatted}%`;
54
+ };