myio-js-library 0.1.522 → 0.1.523

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.
package/dist/index.cjs CHANGED
@@ -1240,7 +1240,7 @@ module.exports = __toCommonJS(index_exports);
1240
1240
  // package.json
1241
1241
  var package_default = {
1242
1242
  name: "myio-js-library",
1243
- version: "0.1.522",
1243
+ version: "0.1.523",
1244
1244
  description: "A clean, standalone JS SDK for MYIO projects",
1245
1245
  license: "MIT",
1246
1246
  repository: "github:gh-myio/myio-js-library",
@@ -67230,10 +67230,41 @@ function openGoalsPanel(params) {
67230
67230
  zIndex: styles.zIndex || 1e4
67231
67231
  };
67232
67232
  const i18n2 = locale === "en-US" ? getEnglishStrings(entityLabel) : getPortugueseStrings(entityLabel);
67233
+ const GLABELS = locale === "en-US" ? {
67234
+ title: "Granularity",
67235
+ month: "Monthly",
67236
+ day: "Daily",
67237
+ hour: "Hourly (soon)",
67238
+ template: "Download template",
67239
+ importBtn: "Import spreadsheet",
67240
+ hintMonth: "12 values",
67241
+ hintDay: "365 values",
67242
+ hintHour: "8,760 \u2014 soon",
67243
+ derived: "Monthly values derived from imported daily detail (read-only).",
67244
+ imported: "Spreadsheet imported.",
67245
+ importErr: "Import error: ",
67246
+ readErr: "Failed to read file."
67247
+ } : {
67248
+ title: "Granularidade",
67249
+ month: "M\xEAs",
67250
+ day: "Di\xE1ria",
67251
+ hour: "Hora (em breve)",
67252
+ template: "Baixar template",
67253
+ importBtn: "Importar planilha",
67254
+ hintMonth: "12 valores",
67255
+ hintDay: "365 valores",
67256
+ hintHour: "8.760 \u2014 em breve",
67257
+ derived: "Valores mensais derivados do detalhe di\xE1rio importado (somente leitura).",
67258
+ imported: "Planilha importada.",
67259
+ importErr: "Erro ao importar: ",
67260
+ readErr: "Falha ao ler o arquivo."
67261
+ };
67233
67262
  const MODAL_ID4 = "goals-modal";
67234
67263
  let modalState = {
67235
67264
  currentTab: "shopping",
67236
67265
  // 'shopping' | 'assets'
67266
+ granularity: "month",
67267
+ // 'month' | 'day' ('hour' = em breve)
67237
67268
  currentYear: (/* @__PURE__ */ new Date()).getFullYear(),
67238
67269
  selectedShoppingId: shoppingList.length > 0 ? shoppingList[0].value : null,
67239
67270
  goalsData: data || null,
@@ -67422,6 +67453,30 @@ function openGoalsPanel(params) {
67422
67453
  </div>
67423
67454
  </div>
67424
67455
 
67456
+ <!-- Granularity selector (M\xEAs | Di\xE1ria | Hora em breve) + template/import -->
67457
+ <div class="myio-goals-section">
67458
+ <div class="myio-goals-section-header">
67459
+ <h3 class="myio-goals-section-title">${GLABELS.title}</h3>
67460
+ <div>
67461
+ <button class="myio-goals-btn-link" data-action="goals-template">\u2B07\uFE0F ${GLABELS.template}</button>
67462
+ <button class="myio-goals-btn-link" data-action="goals-import">\u{1F4E4} ${GLABELS.importBtn}</button>
67463
+ <input type="file" id="goals-import-file" accept=".csv,text/csv" style="display:none" />
67464
+ </div>
67465
+ </div>
67466
+ <div class="myio-goals-gran-chips" style="display:flex;gap:8px;margin-top:8px;flex-wrap:wrap;">
67467
+ ${["month", "day", "hour"].map((g) => {
67468
+ const active = modalState.granularity === g;
67469
+ const disabled = g === "hour";
67470
+ const label = g === "month" ? GLABELS.month : g === "day" ? GLABELS.day : GLABELS.hour;
67471
+ const hint = g === "month" ? GLABELS.hintMonth : g === "day" ? GLABELS.hintDay : GLABELS.hintHour;
67472
+ return `<button type="button" class="myio-goals-gran-chip" data-gran="${g}" ${disabled ? "disabled" : ""}
67473
+ style="padding:6px 12px;border:1px solid ${theme.primaryColor};border-radius:16px;cursor:${disabled ? "not-allowed" : "pointer"};font-size:12px;${active ? `background:${theme.primaryColor};color:#fff;` : `background:#fff;color:${theme.primaryColor};`}${disabled ? "opacity:.5;" : ""}"
67474
+ title="${disabled ? GLABELS.hour : label}">${label} <small>(${hint})</small></button>`;
67475
+ }).join("")}
67476
+ </div>
67477
+ ${modalState.granularity === "day" && Object.keys((getYearData(modalState.currentYear) || {}).daily || {}).length > 0 ? `<div class="myio-goals-gran-note" style="margin-top:8px;font-size:12px;color:#666;">${GLABELS.derived}</div>` : ""}
67478
+ </div>
67479
+
67425
67480
  <!-- Monthly Distribution Section -->
67426
67481
  <div class="myio-goals-section">
67427
67482
  <div class="myio-goals-section-header">
@@ -67473,6 +67528,7 @@ function openGoalsPanel(params) {
67473
67528
  value="${monthly[month.key] || ""}"
67474
67529
  min="0"
67475
67530
  step="0.01"
67531
+ ${modalState.granularity === "day" ? 'disabled title="Derivado do detalhe di\xE1rio \u2014 edite via planilha"' : ""}
67476
67532
  placeholder="0">
67477
67533
  <span class="myio-goals-unit">${unit}</span>
67478
67534
  </div>
@@ -67674,6 +67730,27 @@ function openGoalsPanel(params) {
67674
67730
  container.querySelector('[data-action="auto-fill"]')?.addEventListener("click", () => {
67675
67731
  autoFillMonthly();
67676
67732
  });
67733
+ container.querySelectorAll("[data-gran]").forEach((chip) => {
67734
+ chip.addEventListener("click", () => {
67735
+ if (chip.disabled) return;
67736
+ const g = chip.getAttribute("data-gran");
67737
+ if (g === "hour" || g === modalState.granularity) return;
67738
+ modalState.granularity = g;
67739
+ renderTabContent();
67740
+ });
67741
+ });
67742
+ container.querySelector('[data-action="goals-template"]')?.addEventListener("click", () => {
67743
+ _downloadGoalsTemplate();
67744
+ });
67745
+ const fileInput = container.querySelector("#goals-import-file");
67746
+ container.querySelector('[data-action="goals-import"]')?.addEventListener("click", () => {
67747
+ fileInput?.click();
67748
+ });
67749
+ fileInput?.addEventListener("change", (e) => {
67750
+ const file = e.target.files && e.target.files[0];
67751
+ if (file) _handleGoalsImport(file);
67752
+ e.target.value = "";
67753
+ });
67677
67754
  }
67678
67755
  function attachAssetsTabListeners() {
67679
67756
  const container = document.getElementById("tab-content-area");
@@ -67723,6 +67800,139 @@ function openGoalsPanel(params) {
67723
67800
  }
67724
67801
  modalState.goalsData.years[year.toString()] = data2;
67725
67802
  }
67803
+ function _eachDateOfYear(year) {
67804
+ const out = [];
67805
+ const d = new Date(Date.UTC(year, 0, 1));
67806
+ while (d.getUTCFullYear() === year) {
67807
+ out.push(d.toISOString().slice(0, 10));
67808
+ d.setUTCDate(d.getUTCDate() + 1);
67809
+ }
67810
+ return out;
67811
+ }
67812
+ function _buildGoalsTemplateCsv(granularity, year) {
67813
+ const yd = getYearData(year) || {};
67814
+ if (granularity === "day") {
67815
+ const daily = yd.daily || {};
67816
+ const rows2 = _eachDateOfYear(year).map((iso) => `${iso};${daily[iso] != null ? daily[iso] : 0}`);
67817
+ return "data;valor\n" + rows2.join("\n");
67818
+ }
67819
+ const monthly = yd.monthly || {};
67820
+ const rows = [];
67821
+ for (let i = 1; i <= 12; i++) {
67822
+ const k = String(i).padStart(2, "0");
67823
+ rows.push(`${year}-${k};${monthly[k] != null ? monthly[k] : 0}`);
67824
+ }
67825
+ return "mes;valor\n" + rows.join("\n");
67826
+ }
67827
+ function _downloadGoalsTemplate() {
67828
+ const g = modalState.granularity === "day" ? "day" : "month";
67829
+ const year = modalState.currentYear;
67830
+ const csv = "\uFEFF" + _buildGoalsTemplateCsv(g, year);
67831
+ const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
67832
+ const url = URL.createObjectURL(blob);
67833
+ const a = document.createElement("a");
67834
+ a.href = url;
67835
+ a.download = `metas-${g === "day" ? "diaria" : "mensal"}-${year}.csv`;
67836
+ document.body.appendChild(a);
67837
+ a.click();
67838
+ document.body.removeChild(a);
67839
+ URL.revokeObjectURL(url);
67840
+ }
67841
+ function _parseCsvNumber(s) {
67842
+ let t = String(s == null ? "" : s).trim();
67843
+ if (t.includes(",")) t = t.replace(/\./g, "").replace(",", ".");
67844
+ const v = parseFloat(t);
67845
+ return Number.isFinite(v) ? v : NaN;
67846
+ }
67847
+ function _parseGoalsCsv(text, granularity, year) {
67848
+ const errors = [];
67849
+ const lines = String(text).replace(/^/, "").split(/\r?\n/).map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
67850
+ if (lines.length && !Number.isFinite(_parseCsvNumber(lines[0].split(";")[1] || ""))) lines.shift();
67851
+ if (granularity === "day") {
67852
+ const daily = {};
67853
+ const valid = new Set(_eachDateOfYear(year));
67854
+ lines.forEach((line, idx) => {
67855
+ const [d, v] = line.split(";");
67856
+ const iso = (d || "").trim();
67857
+ const num = _parseCsvNumber(v);
67858
+ if (!valid.has(iso)) {
67859
+ errors.push(`Linha ${idx + 1}: data inv\xE1lida ou fora de ${year}: "${iso}"`);
67860
+ return;
67861
+ }
67862
+ if (!Number.isFinite(num) || num < 0) {
67863
+ errors.push(`Linha ${idx + 1}: valor inv\xE1lido: "${v}"`);
67864
+ return;
67865
+ }
67866
+ daily[iso] = num;
67867
+ });
67868
+ return { granularity: "day", daily, errors };
67869
+ }
67870
+ const monthly = {};
67871
+ lines.forEach((line, idx) => {
67872
+ const [m, v] = line.split(";");
67873
+ const raw = (m || "").trim();
67874
+ const key = raw.includes("-") ? raw.split("-").pop().padStart(2, "0") : raw.padStart(2, "0");
67875
+ const num = _parseCsvNumber(v);
67876
+ if (!/^(0[1-9]|1[0-2])$/.test(key)) {
67877
+ errors.push(`Linha ${idx + 1}: m\xEAs inv\xE1lido: "${raw}"`);
67878
+ return;
67879
+ }
67880
+ if (!Number.isFinite(num) || num < 0) {
67881
+ errors.push(`Linha ${idx + 1}: valor inv\xE1lido: "${v}"`);
67882
+ return;
67883
+ }
67884
+ monthly[key] = num;
67885
+ });
67886
+ return { granularity: "month", monthly, errors };
67887
+ }
67888
+ function _aggregateDailyToMonthly(daily) {
67889
+ const monthly = {};
67890
+ Object.entries(daily || {}).forEach(([iso, val]) => {
67891
+ const mk = iso.slice(5, 7);
67892
+ monthly[mk] = (monthly[mk] || 0) + (parseFloat(val) || 0);
67893
+ });
67894
+ Object.keys(monthly).forEach((k) => {
67895
+ monthly[k] = Math.round(monthly[k] * 100) / 100;
67896
+ });
67897
+ return monthly;
67898
+ }
67899
+ function _handleGoalsImport(file) {
67900
+ const reader = new FileReader();
67901
+ reader.onload = () => {
67902
+ try {
67903
+ const parsed = _parseGoalsCsv(reader.result, modalState.granularity, modalState.currentYear);
67904
+ if (parsed.errors.length) {
67905
+ displayValidationErrors(parsed.errors.slice(0, 8));
67906
+ return;
67907
+ }
67908
+ const yd = getYearData(modalState.currentYear) || { annual: { total: 0, unit: "kWh" }, monthly: {}, assets: {} };
67909
+ if (parsed.granularity === "day") {
67910
+ yd.daily = parsed.daily;
67911
+ yd.granularity = "day";
67912
+ yd.monthly = _aggregateDailyToMonthly(parsed.daily);
67913
+ } else {
67914
+ yd.monthly = parsed.monthly;
67915
+ yd.granularity = "month";
67916
+ delete yd.daily;
67917
+ }
67918
+ if (!yd.annual) yd.annual = { total: 0, unit: "kWh" };
67919
+ if (!yd.annual.total) {
67920
+ const sum = Object.values(yd.monthly).reduce((s, v) => s + (parseFloat(v) || 0), 0);
67921
+ yd.annual.total = Math.round(sum * 100) / 100;
67922
+ }
67923
+ setYearData(modalState.currentYear, yd);
67924
+ modalState.isDirty = true;
67925
+ const errBox = document.getElementById("validation-errors");
67926
+ if (errBox) errBox.style.display = "none";
67927
+ renderTabContent();
67928
+ showSuccessMessage(GLABELS.imported);
67929
+ } catch (err) {
67930
+ displayValidationErrors([GLABELS.importErr + err.message]);
67931
+ }
67932
+ };
67933
+ reader.onerror = () => displayValidationErrors([GLABELS.readErr]);
67934
+ reader.readAsText(file, "utf-8");
67935
+ }
67726
67936
  function loadGoalsData() {
67727
67937
  if (!modalState.goalsData) {
67728
67938
  modalState.goalsData = {
@@ -67808,6 +68018,9 @@ function openGoalsPanel(params) {
67808
68018
  annual: { total, unit },
67809
68019
  monthly,
67810
68020
  assets: yearData.assets || {},
68021
+ // Preserva granularidade e detalhe diário importado (CSV) — não some no save.
68022
+ granularity: yearData.granularity || modalState.granularity || "month",
68023
+ ...yearData.daily ? { daily: yearData.daily } : {},
67811
68024
  metaTag: `${(/* @__PURE__ */ new Date()).toISOString()}|user`
67812
68025
  };
67813
68026
  }
package/dist/index.js CHANGED
@@ -535,7 +535,7 @@ var init_template_card = __esm({
535
535
  // package.json
536
536
  var package_default = {
537
537
  name: "myio-js-library",
538
- version: "0.1.522",
538
+ version: "0.1.523",
539
539
  description: "A clean, standalone JS SDK for MYIO projects",
540
540
  license: "MIT",
541
541
  repository: "github:gh-myio/myio-js-library",
@@ -65800,10 +65800,41 @@ function openGoalsPanel(params) {
65800
65800
  zIndex: styles.zIndex || 1e4
65801
65801
  };
65802
65802
  const i18n2 = locale === "en-US" ? getEnglishStrings(entityLabel) : getPortugueseStrings(entityLabel);
65803
+ const GLABELS = locale === "en-US" ? {
65804
+ title: "Granularity",
65805
+ month: "Monthly",
65806
+ day: "Daily",
65807
+ hour: "Hourly (soon)",
65808
+ template: "Download template",
65809
+ importBtn: "Import spreadsheet",
65810
+ hintMonth: "12 values",
65811
+ hintDay: "365 values",
65812
+ hintHour: "8,760 \u2014 soon",
65813
+ derived: "Monthly values derived from imported daily detail (read-only).",
65814
+ imported: "Spreadsheet imported.",
65815
+ importErr: "Import error: ",
65816
+ readErr: "Failed to read file."
65817
+ } : {
65818
+ title: "Granularidade",
65819
+ month: "M\xEAs",
65820
+ day: "Di\xE1ria",
65821
+ hour: "Hora (em breve)",
65822
+ template: "Baixar template",
65823
+ importBtn: "Importar planilha",
65824
+ hintMonth: "12 valores",
65825
+ hintDay: "365 valores",
65826
+ hintHour: "8.760 \u2014 em breve",
65827
+ derived: "Valores mensais derivados do detalhe di\xE1rio importado (somente leitura).",
65828
+ imported: "Planilha importada.",
65829
+ importErr: "Erro ao importar: ",
65830
+ readErr: "Falha ao ler o arquivo."
65831
+ };
65803
65832
  const MODAL_ID4 = "goals-modal";
65804
65833
  let modalState = {
65805
65834
  currentTab: "shopping",
65806
65835
  // 'shopping' | 'assets'
65836
+ granularity: "month",
65837
+ // 'month' | 'day' ('hour' = em breve)
65807
65838
  currentYear: (/* @__PURE__ */ new Date()).getFullYear(),
65808
65839
  selectedShoppingId: shoppingList.length > 0 ? shoppingList[0].value : null,
65809
65840
  goalsData: data || null,
@@ -65992,6 +66023,30 @@ function openGoalsPanel(params) {
65992
66023
  </div>
65993
66024
  </div>
65994
66025
 
66026
+ <!-- Granularity selector (M\xEAs | Di\xE1ria | Hora em breve) + template/import -->
66027
+ <div class="myio-goals-section">
66028
+ <div class="myio-goals-section-header">
66029
+ <h3 class="myio-goals-section-title">${GLABELS.title}</h3>
66030
+ <div>
66031
+ <button class="myio-goals-btn-link" data-action="goals-template">\u2B07\uFE0F ${GLABELS.template}</button>
66032
+ <button class="myio-goals-btn-link" data-action="goals-import">\u{1F4E4} ${GLABELS.importBtn}</button>
66033
+ <input type="file" id="goals-import-file" accept=".csv,text/csv" style="display:none" />
66034
+ </div>
66035
+ </div>
66036
+ <div class="myio-goals-gran-chips" style="display:flex;gap:8px;margin-top:8px;flex-wrap:wrap;">
66037
+ ${["month", "day", "hour"].map((g) => {
66038
+ const active = modalState.granularity === g;
66039
+ const disabled = g === "hour";
66040
+ const label = g === "month" ? GLABELS.month : g === "day" ? GLABELS.day : GLABELS.hour;
66041
+ const hint = g === "month" ? GLABELS.hintMonth : g === "day" ? GLABELS.hintDay : GLABELS.hintHour;
66042
+ return `<button type="button" class="myio-goals-gran-chip" data-gran="${g}" ${disabled ? "disabled" : ""}
66043
+ style="padding:6px 12px;border:1px solid ${theme.primaryColor};border-radius:16px;cursor:${disabled ? "not-allowed" : "pointer"};font-size:12px;${active ? `background:${theme.primaryColor};color:#fff;` : `background:#fff;color:${theme.primaryColor};`}${disabled ? "opacity:.5;" : ""}"
66044
+ title="${disabled ? GLABELS.hour : label}">${label} <small>(${hint})</small></button>`;
66045
+ }).join("")}
66046
+ </div>
66047
+ ${modalState.granularity === "day" && Object.keys((getYearData(modalState.currentYear) || {}).daily || {}).length > 0 ? `<div class="myio-goals-gran-note" style="margin-top:8px;font-size:12px;color:#666;">${GLABELS.derived}</div>` : ""}
66048
+ </div>
66049
+
65995
66050
  <!-- Monthly Distribution Section -->
65996
66051
  <div class="myio-goals-section">
65997
66052
  <div class="myio-goals-section-header">
@@ -66043,6 +66098,7 @@ function openGoalsPanel(params) {
66043
66098
  value="${monthly[month.key] || ""}"
66044
66099
  min="0"
66045
66100
  step="0.01"
66101
+ ${modalState.granularity === "day" ? 'disabled title="Derivado do detalhe di\xE1rio \u2014 edite via planilha"' : ""}
66046
66102
  placeholder="0">
66047
66103
  <span class="myio-goals-unit">${unit}</span>
66048
66104
  </div>
@@ -66244,6 +66300,27 @@ function openGoalsPanel(params) {
66244
66300
  container.querySelector('[data-action="auto-fill"]')?.addEventListener("click", () => {
66245
66301
  autoFillMonthly();
66246
66302
  });
66303
+ container.querySelectorAll("[data-gran]").forEach((chip) => {
66304
+ chip.addEventListener("click", () => {
66305
+ if (chip.disabled) return;
66306
+ const g = chip.getAttribute("data-gran");
66307
+ if (g === "hour" || g === modalState.granularity) return;
66308
+ modalState.granularity = g;
66309
+ renderTabContent();
66310
+ });
66311
+ });
66312
+ container.querySelector('[data-action="goals-template"]')?.addEventListener("click", () => {
66313
+ _downloadGoalsTemplate();
66314
+ });
66315
+ const fileInput = container.querySelector("#goals-import-file");
66316
+ container.querySelector('[data-action="goals-import"]')?.addEventListener("click", () => {
66317
+ fileInput?.click();
66318
+ });
66319
+ fileInput?.addEventListener("change", (e) => {
66320
+ const file = e.target.files && e.target.files[0];
66321
+ if (file) _handleGoalsImport(file);
66322
+ e.target.value = "";
66323
+ });
66247
66324
  }
66248
66325
  function attachAssetsTabListeners() {
66249
66326
  const container = document.getElementById("tab-content-area");
@@ -66293,6 +66370,139 @@ function openGoalsPanel(params) {
66293
66370
  }
66294
66371
  modalState.goalsData.years[year.toString()] = data2;
66295
66372
  }
66373
+ function _eachDateOfYear(year) {
66374
+ const out = [];
66375
+ const d = new Date(Date.UTC(year, 0, 1));
66376
+ while (d.getUTCFullYear() === year) {
66377
+ out.push(d.toISOString().slice(0, 10));
66378
+ d.setUTCDate(d.getUTCDate() + 1);
66379
+ }
66380
+ return out;
66381
+ }
66382
+ function _buildGoalsTemplateCsv(granularity, year) {
66383
+ const yd = getYearData(year) || {};
66384
+ if (granularity === "day") {
66385
+ const daily = yd.daily || {};
66386
+ const rows2 = _eachDateOfYear(year).map((iso) => `${iso};${daily[iso] != null ? daily[iso] : 0}`);
66387
+ return "data;valor\n" + rows2.join("\n");
66388
+ }
66389
+ const monthly = yd.monthly || {};
66390
+ const rows = [];
66391
+ for (let i = 1; i <= 12; i++) {
66392
+ const k = String(i).padStart(2, "0");
66393
+ rows.push(`${year}-${k};${monthly[k] != null ? monthly[k] : 0}`);
66394
+ }
66395
+ return "mes;valor\n" + rows.join("\n");
66396
+ }
66397
+ function _downloadGoalsTemplate() {
66398
+ const g = modalState.granularity === "day" ? "day" : "month";
66399
+ const year = modalState.currentYear;
66400
+ const csv = "\uFEFF" + _buildGoalsTemplateCsv(g, year);
66401
+ const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
66402
+ const url = URL.createObjectURL(blob);
66403
+ const a = document.createElement("a");
66404
+ a.href = url;
66405
+ a.download = `metas-${g === "day" ? "diaria" : "mensal"}-${year}.csv`;
66406
+ document.body.appendChild(a);
66407
+ a.click();
66408
+ document.body.removeChild(a);
66409
+ URL.revokeObjectURL(url);
66410
+ }
66411
+ function _parseCsvNumber(s) {
66412
+ let t = String(s == null ? "" : s).trim();
66413
+ if (t.includes(",")) t = t.replace(/\./g, "").replace(",", ".");
66414
+ const v = parseFloat(t);
66415
+ return Number.isFinite(v) ? v : NaN;
66416
+ }
66417
+ function _parseGoalsCsv(text, granularity, year) {
66418
+ const errors = [];
66419
+ const lines = String(text).replace(/^/, "").split(/\r?\n/).map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
66420
+ if (lines.length && !Number.isFinite(_parseCsvNumber(lines[0].split(";")[1] || ""))) lines.shift();
66421
+ if (granularity === "day") {
66422
+ const daily = {};
66423
+ const valid = new Set(_eachDateOfYear(year));
66424
+ lines.forEach((line, idx) => {
66425
+ const [d, v] = line.split(";");
66426
+ const iso = (d || "").trim();
66427
+ const num = _parseCsvNumber(v);
66428
+ if (!valid.has(iso)) {
66429
+ errors.push(`Linha ${idx + 1}: data inv\xE1lida ou fora de ${year}: "${iso}"`);
66430
+ return;
66431
+ }
66432
+ if (!Number.isFinite(num) || num < 0) {
66433
+ errors.push(`Linha ${idx + 1}: valor inv\xE1lido: "${v}"`);
66434
+ return;
66435
+ }
66436
+ daily[iso] = num;
66437
+ });
66438
+ return { granularity: "day", daily, errors };
66439
+ }
66440
+ const monthly = {};
66441
+ lines.forEach((line, idx) => {
66442
+ const [m, v] = line.split(";");
66443
+ const raw = (m || "").trim();
66444
+ const key = raw.includes("-") ? raw.split("-").pop().padStart(2, "0") : raw.padStart(2, "0");
66445
+ const num = _parseCsvNumber(v);
66446
+ if (!/^(0[1-9]|1[0-2])$/.test(key)) {
66447
+ errors.push(`Linha ${idx + 1}: m\xEAs inv\xE1lido: "${raw}"`);
66448
+ return;
66449
+ }
66450
+ if (!Number.isFinite(num) || num < 0) {
66451
+ errors.push(`Linha ${idx + 1}: valor inv\xE1lido: "${v}"`);
66452
+ return;
66453
+ }
66454
+ monthly[key] = num;
66455
+ });
66456
+ return { granularity: "month", monthly, errors };
66457
+ }
66458
+ function _aggregateDailyToMonthly(daily) {
66459
+ const monthly = {};
66460
+ Object.entries(daily || {}).forEach(([iso, val]) => {
66461
+ const mk = iso.slice(5, 7);
66462
+ monthly[mk] = (monthly[mk] || 0) + (parseFloat(val) || 0);
66463
+ });
66464
+ Object.keys(monthly).forEach((k) => {
66465
+ monthly[k] = Math.round(monthly[k] * 100) / 100;
66466
+ });
66467
+ return monthly;
66468
+ }
66469
+ function _handleGoalsImport(file) {
66470
+ const reader = new FileReader();
66471
+ reader.onload = () => {
66472
+ try {
66473
+ const parsed = _parseGoalsCsv(reader.result, modalState.granularity, modalState.currentYear);
66474
+ if (parsed.errors.length) {
66475
+ displayValidationErrors(parsed.errors.slice(0, 8));
66476
+ return;
66477
+ }
66478
+ const yd = getYearData(modalState.currentYear) || { annual: { total: 0, unit: "kWh" }, monthly: {}, assets: {} };
66479
+ if (parsed.granularity === "day") {
66480
+ yd.daily = parsed.daily;
66481
+ yd.granularity = "day";
66482
+ yd.monthly = _aggregateDailyToMonthly(parsed.daily);
66483
+ } else {
66484
+ yd.monthly = parsed.monthly;
66485
+ yd.granularity = "month";
66486
+ delete yd.daily;
66487
+ }
66488
+ if (!yd.annual) yd.annual = { total: 0, unit: "kWh" };
66489
+ if (!yd.annual.total) {
66490
+ const sum = Object.values(yd.monthly).reduce((s, v) => s + (parseFloat(v) || 0), 0);
66491
+ yd.annual.total = Math.round(sum * 100) / 100;
66492
+ }
66493
+ setYearData(modalState.currentYear, yd);
66494
+ modalState.isDirty = true;
66495
+ const errBox = document.getElementById("validation-errors");
66496
+ if (errBox) errBox.style.display = "none";
66497
+ renderTabContent();
66498
+ showSuccessMessage(GLABELS.imported);
66499
+ } catch (err) {
66500
+ displayValidationErrors([GLABELS.importErr + err.message]);
66501
+ }
66502
+ };
66503
+ reader.onerror = () => displayValidationErrors([GLABELS.readErr]);
66504
+ reader.readAsText(file, "utf-8");
66505
+ }
66296
66506
  function loadGoalsData() {
66297
66507
  if (!modalState.goalsData) {
66298
66508
  modalState.goalsData = {
@@ -66378,6 +66588,9 @@ function openGoalsPanel(params) {
66378
66588
  annual: { total, unit },
66379
66589
  monthly,
66380
66590
  assets: yearData.assets || {},
66591
+ // Preserva granularidade e detalhe diário importado (CSV) — não some no save.
66592
+ granularity: yearData.granularity || modalState.granularity || "month",
66593
+ ...yearData.daily ? { daily: yearData.daily } : {},
66381
66594
  metaTag: `${(/* @__PURE__ */ new Date()).toISOString()}|user`
66382
66595
  };
66383
66596
  }