diff --git a/agrifine-extension/dist/background.js b/agrifine-extension/dist/background.js index a5de481..3428814 100644 --- a/agrifine-extension/dist/background.js +++ b/agrifine-extension/dist/background.js @@ -12,6 +12,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ getAgRefineUrl: () => (/* binding */ getAgRefineUrl), /* harmony export */ getSyncLog: () => (/* binding */ getSyncLog), +/* harmony export */ pushToAgRefine: () => (/* binding */ pushToAgRefine), /* harmony export */ setAgRefineUrl: () => (/* binding */ setAgRefineUrl), /* harmony export */ syncFromAgRefine: () => (/* binding */ syncFromAgRefine) /* harmony export */ }); @@ -267,38 +268,117 @@ function extractLoads(raw) { } return loads; } -function syncFromAgRefine() { - return _syncFromAgRefine.apply(this, arguments); + +// Injected into AG-Refine tab to write field data back into its localStorage +function writeFieldsToAgRefineTab(fields) { + try { + localStorage.setItem('agrifine_pushed_fields', JSON.stringify(fields)); + localStorage.setItem('agrifine_pushed_at', new Date().toISOString()); + // Dispatch an event so a listening AG-Refine app can react immediately + window.dispatchEvent(new CustomEvent('agrifine:fields-updated', { + detail: { + fields: fields + } + })); + return { + ok: true, + count: fields.length + }; + } catch (err) { + return { + ok: false, + error: err.message + }; + } } -function _syncFromAgRefine() { - _syncFromAgRefine = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee4() { - var configuredUrl, allTabs, agRefineTabs, tab, raw, _yield$chrome$scripti, _yield$chrome$scripti2, result, fields, loads, existing, added, updated, _iterator3, _step3, _loop, log, history, _t7, _t8; - return _regenerator().w(function (_context5) { - while (1) switch (_context5.p = _context5.n) { +function pushToAgRefine(_x2) { + return _pushToAgRefine.apply(this, arguments); +} +function _pushToAgRefine() { + _pushToAgRefine = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee4(profiles) { + var configuredUrl, allTabs, agRefineTabs, tab, _yield$chrome$scripti, _yield$chrome$scripti2, result, _t7; + return _regenerator().w(function (_context4) { + while (1) switch (_context4.p = _context4.n) { case 0: - _context5.n = 1; + _context4.n = 1; return getAgRefineUrl(); case 1: - configuredUrl = _context5.v; - _context5.n = 2; + configuredUrl = _context4.v; + _context4.n = 2; return chrome.tabs.query({}); case 2: - allTabs = _context5.v; + allTabs = _context4.v; agRefineTabs = allTabs.filter(function (t) { return tabMatchesAgRefine(t, configuredUrl); }); if (!(agRefineTabs.length === 0)) { - _context5.n = 3; + _context4.n = 3; break; } - return _context5.a(2, { + return _context4.a(2, { + ok: false, + error: 'No AG-Refine tab found. Open AG-Refine first.' + }); + case 3: + tab = agRefineTabs[0]; + _context4.p = 4; + _context4.n = 5; + return chrome.scripting.executeScript({ + target: { + tabId: tab.id + }, + func: writeFieldsToAgRefineTab, + args: [profiles] + }); + case 5: + _yield$chrome$scripti = _context4.v; + _yield$chrome$scripti2 = _slicedToArray(_yield$chrome$scripti, 1); + result = _yield$chrome$scripti2[0]; + return _context4.a(2, result.result); + case 6: + _context4.p = 6; + _t7 = _context4.v; + return _context4.a(2, { + ok: false, + error: "Cannot write to AG-Refine tab: ".concat(_t7.message) + }); + } + }, _callee4, null, [[4, 6]]); + })); + return _pushToAgRefine.apply(this, arguments); +} +function syncFromAgRefine() { + return _syncFromAgRefine.apply(this, arguments); +} +function _syncFromAgRefine() { + _syncFromAgRefine = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee5() { + var configuredUrl, allTabs, agRefineTabs, tab, raw, _yield$chrome$scripti3, _yield$chrome$scripti4, result, fields, loads, existing, added, updated, _iterator3, _step3, _loop, log, history, _t8, _t9; + return _regenerator().w(function (_context6) { + while (1) switch (_context6.p = _context6.n) { + case 0: + _context6.n = 1; + return getAgRefineUrl(); + case 1: + configuredUrl = _context6.v; + _context6.n = 2; + return chrome.tabs.query({}); + case 2: + allTabs = _context6.v; + agRefineTabs = allTabs.filter(function (t) { + return tabMatchesAgRefine(t, configuredUrl); + }); + if (!(agRefineTabs.length === 0)) { + _context6.n = 3; + break; + } + return _context6.a(2, { ok: false, error: 'No AG-Refine tab found. Open AG-Refine in a browser tab first.' }); case 3: tab = agRefineTabs[0]; - _context5.p = 4; - _context5.n = 5; + _context6.p = 4; + _context6.n = 5; return chrome.scripting.executeScript({ target: { tabId: tab.id @@ -306,41 +386,41 @@ function _syncFromAgRefine() { func: scrapeAgRefineTab }); case 5: - _yield$chrome$scripti = _context5.v; - _yield$chrome$scripti2 = _slicedToArray(_yield$chrome$scripti, 1); - result = _yield$chrome$scripti2[0]; + _yield$chrome$scripti3 = _context6.v; + _yield$chrome$scripti4 = _slicedToArray(_yield$chrome$scripti3, 1); + result = _yield$chrome$scripti4[0]; raw = result.result; - _context5.n = 7; + _context6.n = 7; break; case 6: - _context5.p = 6; - _t7 = _context5.v; - return _context5.a(2, { + _context6.p = 6; + _t8 = _context6.v; + return _context6.a(2, { ok: false, - error: "Cannot read AG-Refine tab: ".concat(_t7.message) + error: "Cannot read AG-Refine tab: ".concat(_t8.message) }); case 7: fields = extractFields(raw); loads = extractLoads(raw); // Merge fields — update existing by name, insert new ones - _context5.n = 8; + _context6.n = 8; return (0,_storage_js__WEBPACK_IMPORTED_MODULE_0__.getFieldProfiles)(); case 8: - existing = _context5.v; + existing = _context6.v; added = 0; updated = 0; _iterator3 = _createForOfIteratorHelper(fields); - _context5.p = 9; + _context6.p = 9; _loop = /*#__PURE__*/_regenerator().m(function _loop() { var f, match, _match$coordinates, _match$cropHistory, _match$notes, _match$cluId, merged; - return _regenerator().w(function (_context4) { - while (1) switch (_context4.n) { + return _regenerator().w(function (_context5) { + while (1) switch (_context5.n) { case 0: f = _step3.value; match = existing.find(function (e) { return e.name.toLowerCase() === f.name.toLowerCase(); }); if (!match) { - _context4.n = 2; + _context5.n = 2; break; } // Merge: fill in missing data without overwriting user edits @@ -351,43 +431,43 @@ function _syncFromAgRefine() { cluId: (_match$cluId = match.cluId) !== null && _match$cluId !== void 0 ? _match$cluId : f.cluId, _source: 'ag-refine-merged' }); - _context4.n = 1; + _context5.n = 1; return (0,_storage_js__WEBPACK_IMPORTED_MODULE_0__.saveFieldProfile)(merged); case 1: updated++; - _context4.n = 4; + _context5.n = 4; break; case 2: - _context4.n = 3; + _context5.n = 3; return (0,_storage_js__WEBPACK_IMPORTED_MODULE_0__.saveFieldProfile)(f); case 3: added++; case 4: - return _context4.a(2); + return _context5.a(2); } }, _loop); }); _iterator3.s(); case 10: if ((_step3 = _iterator3.n()).done) { - _context5.n = 12; + _context6.n = 12; break; } - return _context5.d(_regeneratorValues(_loop()), 11); + return _context6.d(_regeneratorValues(_loop()), 11); case 11: - _context5.n = 10; + _context6.n = 10; break; case 12: - _context5.n = 14; + _context6.n = 14; break; case 13: - _context5.p = 13; - _t8 = _context5.v; - _iterator3.e(_t8); + _context6.p = 13; + _t9 = _context6.v; + _iterator3.e(_t9); case 14: - _context5.p = 14; + _context6.p = 14; _iterator3.f(); - return _context5.f(14); + return _context6.f(14); case 15: log = { at: new Date().toISOString(), @@ -397,15 +477,15 @@ function _syncFromAgRefine() { loadsFound: loads.length, rawKeys: Object.keys(_objectSpread(_objectSpread({}, raw.localStorage), raw.sessionStorage)) }; - _context5.n = 16; + _context6.n = 16; return getSyncLog(); case 16: - history = _context5.v; + history = _context6.v; history.unshift(log); - _context5.n = 17; + _context6.n = 17; return (0,_storage_js__WEBPACK_IMPORTED_MODULE_0__.localSet)(SYNC_LOG_KEY, history.slice(0, 20)); case 17: - return _context5.a(2, { + return _context6.a(2, { ok: true, added: added, updated: updated, @@ -414,7 +494,7 @@ function _syncFromAgRefine() { tabUrl: tab.url }); } - }, _callee4, null, [[9, 13, 14, 15], [4, 6]]); + }, _callee5, null, [[9, 13, 14, 15], [4, 6]]); })); return _syncFromAgRefine.apply(this, arguments); } @@ -1391,6 +1471,16 @@ chrome.runtime.onMessage.addListener(function (message, _sender, sendResponse) { }); }); return true; + case 'AGREFINE_PUSH': + (0,_utils_storage_js__WEBPACK_IMPORTED_MODULE_0__.getFieldProfiles)().then(function (profiles) { + return (0,_utils_agrefine_bridge_js__WEBPACK_IMPORTED_MODULE_2__.pushToAgRefine)(profiles); + }).then(sendResponse)["catch"](function (err) { + return sendResponse({ + ok: false, + error: err.message + }); + }); + return true; default: return false; } diff --git a/agrifine-extension/dist/sidebar.css b/agrifine-extension/dist/sidebar.css index 8197fcb..b8132af 100644 --- a/agrifine-extension/dist/sidebar.css +++ b/agrifine-extension/dist/sidebar.css @@ -579,6 +579,9 @@ video { .mt-2 { margin-top: 0.5rem; } +.mt-2\.5 { + margin-top: 0.625rem; +} .mt-3 { margin-top: 0.75rem; } @@ -609,6 +612,9 @@ video { .h-3 { height: 0.75rem; } +.h-3\.5 { + height: 0.875rem; +} .h-4 { height: 1rem; } @@ -645,6 +651,9 @@ video { .w-3 { width: 0.75rem; } +.w-3\.5 { + width: 0.875rem; +} .w-4 { width: 1rem; } @@ -663,6 +672,9 @@ video { .min-w-0 { min-width: 0px; } +.max-w-\[180px\] { + max-width: 180px; +} .max-w-\[85\%\] { max-width: 85%; } @@ -730,11 +742,21 @@ video { .gap-y-0\.5 { row-gap: 0.125rem; } +.space-y-0\.5 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.125rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.125rem * var(--tw-space-y-reverse)); +} .space-y-1 > :not([hidden]) ~ :not([hidden]) { --tw-space-y-reverse: 0; margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse))); margin-bottom: calc(0.25rem * var(--tw-space-y-reverse)); } +.space-y-1\.5 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.375rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.375rem * var(--tw-space-y-reverse)); +} .space-y-2 > :not([hidden]) ~ :not([hidden]) { --tw-space-y-reverse: 0; margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse))); @@ -857,6 +879,10 @@ video { padding-left: 0.25rem; padding-right: 0.25rem; } +.px-1\.5 { + padding-left: 0.375rem; + padding-right: 0.375rem; +} .px-2 { padding-left: 0.5rem; padding-right: 0.5rem; @@ -968,6 +994,9 @@ video { .uppercase { text-transform: uppercase; } +.italic { + font-style: italic; +} .leading-none { line-height: 1; } @@ -1014,6 +1043,10 @@ video { --tw-text-opacity: 1; color: rgb(107 114 128 / var(--tw-text-opacity, 1)); } +.text-gray-600 { + --tw-text-opacity: 1; + color: rgb(75 85 99 / var(--tw-text-opacity, 1)); +} .text-night-300 { --tw-text-opacity: 1; color: rgb(61 79 102 / var(--tw-text-opacity, 1)); @@ -1026,6 +1059,9 @@ video { --tw-text-opacity: 1; color: rgb(255 255 255 / var(--tw-text-opacity, 1)); } +.underline { + text-decoration-line: underline; +} .accent-agri-500 { accent-color: #22c55e; } @@ -1226,6 +1262,9 @@ body { .last\:border-0:last-child { border-width: 0px; } +.last\:pb-0:last-child { + padding-bottom: 0px; +} .hover\:border-agri-500:hover { --tw-border-opacity: 1; border-color: rgb(34 197 94 / var(--tw-border-opacity, 1)); @@ -1261,6 +1300,9 @@ body { .hover\:underline:hover { text-decoration-line: underline; } +.hover\:no-underline:hover { + text-decoration-line: none; +} .disabled\:bg-night-500:disabled { --tw-bg-opacity: 1; background-color: rgb(37 48 71 / var(--tw-bg-opacity, 1)); diff --git a/agrifine-extension/dist/sidebar.js b/agrifine-extension/dist/sidebar.js index 62f4644..9937731 100644 --- a/agrifine-extension/dist/sidebar.js +++ b/agrifine-extension/dist/sidebar.js @@ -1794,6 +1794,18 @@ __webpack_require__.r(__webpack_exports__); /* harmony export */ }); /* harmony import */ var _utils_storage_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../utils/storage.js */ "./src/utils/storage.js"); /* harmony import */ var _utils_api_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../utils/api.js */ "./src/utils/api.js"); +function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } +function _regeneratorValues(e) { if (null != e) { var t = e["function" == typeof Symbol && Symbol.iterator || "@@iterator"], r = 0; if (t) return t.call(e); if ("function" == typeof e.next) return e; if (!isNaN(e.length)) return { next: function next() { return e && r >= e.length && (e = void 0), { value: e && e[r++], done: !e }; } }; } throw new TypeError(_typeof(e) + " is not iterable"); } +function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } +function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } +function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } +function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } +function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } +function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; } +function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); } +function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } +function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); } +function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); } function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } @@ -1817,39 +1829,39 @@ function tryDocServer(_x) { return _tryDocServer.apply(this, arguments); } function _tryDocServer() { - _tryDocServer = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee7(file) { - var fd, res, _yield$res$json, text, _t5; - return _regenerator().w(function (_context7) { - while (1) switch (_context7.p = _context7.n) { + _tryDocServer = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee0(file) { + var fd, res, _yield$res$json, text, _t6; + return _regenerator().w(function (_context1) { + while (1) switch (_context1.p = _context1.n) { case 0: - _context7.p = 0; + _context1.p = 0; fd = new FormData(); fd.append('file', file); - _context7.n = 1; + _context1.n = 1; return fetch("".concat(DOC_SERVER, "/parse"), { method: 'POST', body: fd }); case 1: - res = _context7.v; + res = _context1.v; if (res.ok) { - _context7.n = 2; + _context1.n = 2; break; } - return _context7.a(2, null); + return _context1.a(2, null); case 2: - _context7.n = 3; + _context1.n = 3; return res.json(); case 3: - _yield$res$json = _context7.v; + _yield$res$json = _context1.v; text = _yield$res$json.text; - return _context7.a(2, text !== null && text !== void 0 ? text : null); + return _context1.a(2, text !== null && text !== void 0 ? text : null); case 4: - _context7.p = 4; - _t5 = _context7.v; - return _context7.a(2, null); + _context1.p = 4; + _t6 = _context1.v; + return _context1.a(2, null); } - }, _callee7, null, [[0, 4]]); + }, _callee0, null, [[0, 4]]); })); return _tryDocServer.apply(this, arguments); } @@ -1863,7 +1875,8 @@ function DataIngestModule() { return _regenerator().w(function (_context) { while (1) switch (_context.n) { case 0: - container.innerHTML = "\n
Drop CSV, Excel, or PDF here
\nor click to select a file
\n \nDrop CSV, Excel, or PDF here
\nor click to select a file
\n \nNo files ingested yet.
\nUpload a CSV, Excel, or PDF file above.
\n").concat(f.filename, "
\n").concat(new Date(f.uploadedAt).toLocaleDateString(), "
\n".concat(f.preview, "") : '', "\n
").concat(f.filename, "
\n").concat(new Date(f.uploadedAt).toLocaleDateString(), "
\n".concat(f.preview, "") : '', "\n ").concat(_this6._hasFieldData(f) ? "
\u2197 Contains field data \xB7
") : '', "\nNo field profiles yet.
\nCreate a profile for each field in your operation.
\n".concat(log.length - 5, " older entries hidden
") : '', "\nNo field profiles yet.
\nCreate a profile or sync from AG-Refine.
\n\uD83D\uDCCD ".concat(p.coordinates.lat.toFixed(4), ", ").concat(p.coordinates.lon.toFixed(4), "
") : '', "\n ").concat(p.notes ? "\uD83D\uDCDD ".concat(p.notes, "
") : '', "\n ").concat((_p$_source = p._source) !== null && _p$_source !== void 0 && _p$_source.includes('ag-refine') ? "\u2197 Synced from AG-Refine
" : '', "\n ").concat(agRefineUrl ? "Open in AG-Refine \u2197") : '', "\nWeather data: Phase 6
\nCarbon potential: Phase 7
\nAdded ").concat(new Date(p.createdAt).toLocaleDateString(), "
\nCrop History
\nHarvest Records
\n\uD83D\uDCCD ".concat(p.coordinates.lat.toFixed(4), ", ").concat(p.coordinates.lon.toFixed(4), "
") : '', "\n ").concat(p.notes ? "\uD83D\uDCDD ".concat(p.notes, "
") : '', "\n ").concat(cropHistoryHtml, "\n ").concat(harvestHtml, "\n ").concat(!cropHistoryHtml && !harvestHtml ? "No crop history yet \u2014 ingest a harvest file to populate.
" : '', "\nAdded ").concat(new Date(p.createdAt).toLocaleDateString(), "
\n${f.preview}` : ''}
+ ${this._hasFieldData(f) ? `↗ Contains field data ·
` : ''} `).join(''); @@ -259,6 +387,22 @@ export function DataIngestModule() { await this._renderFileList(container); }); }); + + listEl.querySelectorAll('.reimport-btn').forEach((btn) => { + btn.addEventListener('click', async () => { + const file = files.find((f) => f.id === btn.dataset.id); + if (file) await this._offerFieldImport(file, container); + }); + }); + }, + + _hasFieldData(file) { + const sd = file.structuredData; + if (!sd || sd.parse_error) return false; + return ( + (Array.isArray(sd.fields) && sd.fields.length > 0) || + (Array.isArray(sd.field_names) && sd.field_names.length > 0) + ); }, }; } diff --git a/agrifine-extension/src/modules/field-profile/index.js b/agrifine-extension/src/modules/field-profile/index.js index 04f6fa8..16cbd63 100644 --- a/agrifine-extension/src/modules/field-profile/index.js +++ b/agrifine-extension/src/modules/field-profile/index.js @@ -1,5 +1,5 @@ import { getFieldProfiles, saveFieldProfile, deleteFieldProfile } from '../../utils/storage.js'; -import { getAgRefineUrl, setAgRefineUrl } from '../../utils/agrefine-bridge.js'; +import { getAgRefineUrl, getSyncLog } from '../../utils/agrefine-bridge.js'; export function FieldProfileModule() { let showForm = false; @@ -21,12 +21,19 @@ export function FieldProfileModule() { New Field - + @@ -62,10 +69,14 @@ export function FieldProfileModule() { + + + `; this._bindEvents(container); await this._renderList(container); + await this._renderSyncLog(container); }, _bindEvents(container) { @@ -74,7 +85,13 @@ export function FieldProfileModule() { container.querySelector('#fp-form').classList.toggle('hidden', !showForm); }); - container.querySelector('#fp-agrefine-sync-btn').addEventListener('click', () => this._syncAgRefine(container)); + container.querySelector('#fp-agrefine-sync-btn').addEventListener('click', () => + this._pullFromAgRefine(container) + ); + + container.querySelector('#fp-agrefine-push-btn').addEventListener('click', () => + this._pushToAgRefine(container) + ); container.querySelector('#fp-cancel-btn').addEventListener('click', () => { showForm = false; @@ -96,11 +113,12 @@ export function FieldProfileModule() { lon: parseFloat(container.querySelector('#fp-lon').value) || null, }, notes: container.querySelector('#fp-notes').value.trim() || null, - cropHistory: [], // populated from ingested data in Phase 3 - harvestRecords: [], // populated from ingested CSVs in Phase 3 - weatherData: null, // Phase 6 - carbonPotential: null, // Phase 7 + cropHistory: [], + harvestRecords: [], + weatherData: null, + carbonPotential: null, createdAt: new Date().toISOString(), + _source: 'manual', }; await saveFieldProfile(profile); @@ -110,9 +128,9 @@ export function FieldProfileModule() { }); }, - async _syncAgRefine(container) { + async _pullFromAgRefine(container) { const statusEl = container.querySelector('#fp-sync-status'); - statusEl.textContent = 'Connecting to AG-Refine tab…'; + statusEl.textContent = 'Connecting to AG-Refine…'; statusEl.style.color = '#3d4f66'; const result = await chrome.runtime.sendMessage({ type: 'AGREFINE_SYNC' }); @@ -120,18 +138,80 @@ export function FieldProfileModule() { if (!result.ok) { statusEl.textContent = `⚠ ${result.error}`; statusEl.style.color = '#f87171'; - setTimeout(() => { statusEl.textContent = ''; }, 5000); + setTimeout(() => { statusEl.textContent = ''; statusEl.style.color = '#3d4f66'; }, 6000); return; } const parts = []; - if (result.added) parts.push(`${result.added} added`); + if (result.added) parts.push(`${result.added} field${result.added !== 1 ? 's' : ''} added`); if (result.updated) parts.push(`${result.updated} updated`); if (result.loadsFound) parts.push(`${result.loadsFound} loads found`); - statusEl.textContent = parts.length ? `✓ Synced: ${parts.join(', ')}` : '✓ No new fields found in AG-Refine'; + statusEl.textContent = parts.length + ? `✓ Pull complete — ${parts.join(', ')}` + : '✓ No new fields in AG-Refine'; statusEl.style.color = '#4ade80'; - setTimeout(() => { statusEl.textContent = ''; }, 4000); + setTimeout(() => { statusEl.textContent = ''; statusEl.style.color = '#3d4f66'; }, 5000); + await this._renderList(container); + await this._renderSyncLog(container); + }, + + async _pushToAgRefine(container) { + const statusEl = container.querySelector('#fp-sync-status'); + statusEl.textContent = 'Pushing to AG-Refine…'; + statusEl.style.color = '#3d4f66'; + + const result = await chrome.runtime.sendMessage({ type: 'AGREFINE_PUSH' }); + + if (!result.ok) { + statusEl.textContent = `⚠ ${result.error}`; + statusEl.style.color = '#f87171'; + setTimeout(() => { statusEl.textContent = ''; statusEl.style.color = '#3d4f66'; }, 6000); + return; + } + + statusEl.textContent = `✓ Pushed ${result.count} field${result.count !== 1 ? 's' : ''} to AG-Refine`; + statusEl.style.color = '#4ade80'; + setTimeout(() => { statusEl.textContent = ''; statusEl.style.color = '#3d4f66'; }, 4000); + }, + + async _renderSyncLog(container) { + const log = await getSyncLog(); + const logEl = container.querySelector('#fp-sync-log'); + + if (log.length === 0) { + logEl.classList.add('hidden'); + return; + } + + const latest = log[0]; + logEl.classList.remove('hidden'); + logEl.innerHTML = ` +${log.length - 5} older entries hidden
` : ''} +No field profiles yet.
-Create a profile for each field in your operation.
+Create a profile or sync from AG-Refine.
`; return; } - listEl.innerHTML = profiles.map((p) => ` -Crop History
+Harvest Records
+📍 ${p.coordinates.lat.toFixed(4)}, ${p.coordinates.lon.toFixed(4)}
` + : ''} + ${p.notes ? `📝 ${p.notes}
` : ''} + ${cropHistoryHtml} + ${harvestHtml} + ${!cropHistoryHtml && !harvestHtml + ? `No crop history yet — ingest a harvest file to populate.
` + : ''} +Added ${new Date(p.createdAt).toLocaleDateString()}
+