149 const [showModal, setShowModal] = useState(false);
150
151 async function fetchLastWateredInternal() {
152 if (!syncId) return;
153 setIsLoading(true);
154 try {
155 const response = await fetch(`/api/${syncId}/last-watered`);
156 if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
157 const data = await response.json();
158 setLastWatered(data.lastWatered);
159 } catch (error) {
160 console.error("Error fetching last watered:", error);
161 setLastWatered(null); // Set to a default or error state
162 }
165
166 useEffect(() => {
167 fetchLastWateredInternal();
168 }, [syncId]);
169
220 <WatererSelectionModal
221 onClose={() => setShowModal(false)}
222 onWatered={fetchLastWateredInternal} // Pass the internal fetcher
223 />
224 )}
237 const [sortedWaterers, setSortedWaterers] = useState([]);
238
239 async function fetchWaterersInternal() {
240 if (!syncId) return;
241 try {
242 const response = await fetch(`/api/${syncId}/waterers`);
243 if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
244 const data = await response.json();
245 setWaterers(Array.isArray(data) ? data : []);
246 } catch (error) {
247 console.error("Error fetching waterers:", error);
248 setWaterers([]);
249 }
250 }
251
252 async function fetchRecentWaterersInternal() {
253 if (!syncId) return;
254 try {
255 const response = await fetch(`/api/${syncId}/history`);
256 if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
257 const data = await response.json();
264 setRecentWaterers(uniqueNames);
265 } catch (error) {
266 console.error("Error fetching recent waterers:", error);
267 setRecentWaterers([]);
268 }
303
304 useEffect(() => {
305 fetchWaterersInternal();
306 fetchRecentWaterersInternal();
307 }, [syncId]);
308
312
313 try {
314 await fetch(`/api/${syncId}/water`, {
315 method: "POST",
316 headers: { "Content-Type": "application/json" },
323 if (newWatererName.trim() && !selectedWaterer) {
324 // Add the new waterer to the list
325 await fetch(`/api/${syncId}/waterers`, {
326 method: "POST",
327 headers: { "Content-Type": "application/json" },
330 }
331
332 onWatered(); // This will call fetchLastWateredInternal in Plant component
333 onClose();
334 } catch (error) {
475 };
476
477 async function fetchDataInternal() {
478 if (!syncId) return;
479 setIsLoading(true);
481 try {
482 const [waterersResponse, historyResponse] = await Promise.all([
483 fetch(`/api/${syncId}/waterers`),
484 fetch(`/api/${syncId}/history`),
485 ]);
486 if (!waterersResponse.ok) throw new Error(`Waterers fetch error! status: ${waterersResponse.status}`);
487 if (!historyResponse.ok) throw new Error(`History fetch error! status: ${historyResponse.status}`);
488
489 const waterersData = await waterersResponse.json();
493 setHistory(Array.isArray(historyData) ? historyData : []);
494 } catch (error) {
495 console.error("Error fetching data for history:", error);
496 setWaterers([]);
497 setHistory([]);
502
503 useEffect(() => {
504 fetchDataInternal();
505 }, [syncId]);
506
510 setIsProcessing(true);
511 try {
512 await fetch(`/api/${syncId}/history/${entryId}`, { method: "DELETE" });
513 fetchDataInternal(); // Refresh
514 } catch (error) {
515 console.error("Error deleting history entry:", error);
673 const [isProcessing, setIsProcessing] = useState(false);
674
675 async function fetchWaterersInternal() {
676 if (!syncId) return;
677 setIsLoading(true);
678 setIsProcessing(true);
679 try {
680 const waterersResponse = await fetch(`/api/${syncId}/waterers`);
681 if (!waterersResponse.ok) throw new Error(`Waterers fetch error! status: ${waterersResponse.status}`);
682
683 const waterersData = await waterersResponse.json();
684 setWaterers(Array.isArray(waterersData) ? waterersData : []);
685 } catch (error) {
686 console.error("Error fetching waterers:", error);
687 setWaterers([]);
688 }
692
693 useEffect(() => {
694 fetchWaterersInternal();
695 }, [syncId]);
696
699 setIsProcessing(true);
700 try {
701 const response = await fetch(`/api/${syncId}/waterers`, {
702 method: "POST",
703 headers: { "Content-Type": "application/json" },
709 }
710 setNewWatererName("");
711 fetchWaterersInternal(); // Refresh waterers
712 } catch (error) {
713 console.error("Error adding waterer:", error);
723 setIsProcessing(true);
724 try {
725 await fetch(`/api/${syncId}/waterers/${watererId}`, {
726 method: "PUT",
727 headers: { "Content-Type": "application/json" },
728 body: JSON.stringify({ name: newName.trim() }),
729 });
730 fetchWaterersInternal(); // Refresh
731 } catch (error) {
732 console.error("Error renaming waterer:", error);
742 setIsProcessing(true);
743 try {
744 await fetch(`/api/${syncId}/waterers/${watererId}`, { method: "DELETE" });
745 fetchWaterersInternal(); // Refresh
746 } catch (error) {
747 console.error("Error deleting waterer:", error);
1034 });
1035
1036 self.addEventListener('fetch', event => {
1037 // For navigation requests, try network first, then cache
1038 if (event.request.mode === 'navigate') {
1039 event.respondWith(
1040 fetch(event.request)
1041 .then(response => {
1042 // Cache a copy of the response
1048 })
1049 .catch(() => {
1050 // If network fetch fails, fallback to cache
1051 return caches.match(event.request);
1052 })
1057 // For all other requests, try network, don't cache
1058 event.respondWith(
1059 fetch(event.request)
1060 .catch(() => caches.match(event.request))
1061 );
1085 [currentSyncId],
1086 );
1087 // Fetch the newly created row to ensure consistent data structure
1088 result = await sqlite.execute(
1089 `SELECT waterers, watering_log, last_watered_timestamp FROM instances WHERE sync_id = ?`,