From 31b58b7151654a65ffe34873c98e64e202c3c521 Mon Sep 17 00:00:00 2001 From: alireza Date: Thu, 18 Dec 2025 19:39:05 +0330 Subject: [PATCH] fix bug and version check --- next.config.ts | 2 +- package.json | 2 +- public/sw.js | 21 ++- public/version.json | 3 + scripts/generate-version.js | 38 +++++ src/app/daily-report/page.tsx | 50 +++++-- src/app/layout.tsx | 2 + src/components/ServiceWorkerRegistration.tsx | 59 ++++++-- src/components/UpdateNotification.tsx | 140 +++++++++++++++++++ src/components/daily-report/SummaryCard.tsx | 2 +- src/components/daily-report/types.ts | 4 +- src/lib/version.ts | 12 ++ 12 files changed, 300 insertions(+), 35 deletions(-) create mode 100644 public/version.json create mode 100644 scripts/generate-version.js create mode 100644 src/components/UpdateNotification.tsx create mode 100644 src/lib/version.ts diff --git a/next.config.ts b/next.config.ts index ad9d8c4..2e9099e 100644 --- a/next.config.ts +++ b/next.config.ts @@ -3,7 +3,7 @@ import type { NextConfig } from 'next' const nextConfig: NextConfig = { reactStrictMode: true, experimental: {}, - turbopack: { root: __dirname } + turbopack: { root: __dirname }, } export default nextConfig diff --git a/package.json b/package.json index d62b7b9..70a752a 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "private": true, "scripts": { "dev": "next dev", - "build": "next build", + "build": "node scripts/generate-version.js && next build", "start": "next start", "lint": "eslint" }, diff --git a/public/sw.js b/public/sw.js index cdbf02a..99cd753 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,5 +1,5 @@ -const CACHE_NAME = 'greenhome-v2'; -const STATIC_CACHE_NAME = 'greenhome-static-v2'; +const CACHE_NAME = 'greenhome-1766074058129'; +const STATIC_CACHE_NAME = 'greenhome-static-1766074058129'; // Static assets to cache on install const STATIC_FILES_TO_CACHE = [ @@ -32,7 +32,8 @@ self.addEventListener('install', (event) => { } catch (error) { console.log('Some static files failed to cache:', error); } - await self.skipWaiting(); + // Don't skip waiting automatically - let user decide when to update + // await self.skipWaiting(); })() ); }); @@ -53,6 +54,20 @@ self.addEventListener('activate', (event) => { ); }); +// Listen for messages from clients (e.g., when user clicks update button) +self.addEventListener('message', (event) => { + if (event.data && event.data.type === 'SKIP_WAITING') { + self.skipWaiting().then(() => { + // Notify all clients about update + return self.clients.matchAll(); + }).then((clients) => { + clients.forEach((client) => { + client.postMessage({ type: 'SW_UPDATED' }); + }); + }); + } +}); + self.addEventListener('fetch', (event) => { const { request } = event; const url = new URL(request.url); diff --git a/public/version.json b/public/version.json new file mode 100644 index 0000000..0a045f7 --- /dev/null +++ b/public/version.json @@ -0,0 +1,3 @@ +{ + "version": "1766074058129" +} \ No newline at end of file diff --git a/scripts/generate-version.js b/scripts/generate-version.js new file mode 100644 index 0000000..38dfacd --- /dev/null +++ b/scripts/generate-version.js @@ -0,0 +1,38 @@ +const fs = require('fs'); +const path = require('path'); + +const version = Date.now().toString(); +const versionFile = path.join(__dirname, '../public/version.json'); + +// Ensure public directory exists +const publicDir = path.dirname(versionFile); +if (!fs.existsSync(publicDir)) { + fs.mkdirSync(publicDir, { recursive: true }); +} + +fs.writeFileSync(versionFile, JSON.stringify({ version }, null, 2)); + +// Also write to .env.local for Next.js to use +const envFile = path.join(__dirname, '../.env.local'); +const envContent = `NEXT_PUBLIC_APP_VERSION=${version}\n`; +fs.writeFileSync(envFile, envContent); + +// Update cache name in sw.js based on version +const swPath = path.join(__dirname, '../public/sw.js'); +if (fs.existsSync(swPath)) { + let swContent = fs.readFileSync(swPath, 'utf8'); + // Update cache names with version - using more specific regex + const cacheNameRegex = /const CACHE_NAME\s*=\s*['"][^'"]*['"]/; + const staticCacheNameRegex = /const STATIC_CACHE_NAME\s*=\s*['"][^'"]*['"]/; + + if (cacheNameRegex.test(swContent)) { + swContent = swContent.replace(cacheNameRegex, `const CACHE_NAME = 'greenhome-${version}'`); + } + if (staticCacheNameRegex.test(swContent)) { + swContent = swContent.replace(staticCacheNameRegex, `const STATIC_CACHE_NAME = 'greenhome-static-${version}'`); + } + fs.writeFileSync(swPath, swContent); +} + +console.log(`Version generated: ${version}`); + diff --git a/src/app/daily-report/page.tsx b/src/app/daily-report/page.tsx index d607c51..0749e6c 100644 --- a/src/app/daily-report/page.tsx +++ b/src/app/daily-report/page.tsx @@ -482,23 +482,43 @@ function DailyReportContent() { {/* Tabs */}
-
- {TABS.map(tab => ( - - ))} + {/* Segmented Control for Mobile */} +
+
+ {TABS.map(tab => ( + + ))} +
+ + {/* Desktop Tabs */} +
+ {TABS.map(tab => ( + + ))} +
-
+
{/* Summary Tab */} {activeTab === 'summary' && ( + {children} diff --git a/src/components/ServiceWorkerRegistration.tsx b/src/components/ServiceWorkerRegistration.tsx index 4591c6b..fb342f8 100644 --- a/src/components/ServiceWorkerRegistration.tsx +++ b/src/components/ServiceWorkerRegistration.tsx @@ -8,26 +8,51 @@ export default function ServiceWorkerRegistration() { typeof window !== 'undefined' && 'serviceWorker' in navigator ) { + let registration: ServiceWorkerRegistration | null = null; + let checkInterval: NodeJS.Timeout | null = null; + + const handleMessage = (event: MessageEvent) => { + if (event.data && event.data.type === 'SW_UPDATED') { + // Service worker has updated, reload the page + window.location.reload(); + } + }; + + const handleVisibilityChange = () => { + if (!document.hidden && registration) { + registration.update(); + } + }; + + const handleFocus = () => { + if (registration) { + registration.update(); + } + }; + const registerServiceWorker = async () => { try { - const registration = await navigator.serviceWorker.register('/sw.js', { + registration = await navigator.serviceWorker.register('/sw.js', { scope: '/', }); console.log('Service Worker registered successfully:', registration.scope); - // Handle updates - registration.addEventListener('updatefound', () => { - const newWorker = registration.installing; - if (newWorker) { - newWorker.addEventListener('statechange', () => { - if (newWorker.state === 'installed' && navigator.serviceWorker.controller) { - // New service worker available - console.log('New service worker available'); - } - }); + // Listen for messages from service worker + navigator.serviceWorker.addEventListener('message', handleMessage); + + // Check for updates periodically + checkInterval = setInterval(() => { + if (registration) { + registration.update(); } - }); + }, 60000); // Check every minute + + // Check for updates when page becomes visible + document.addEventListener('visibilitychange', handleVisibilityChange); + + // Check for updates on page focus + window.addEventListener('focus', handleFocus); } catch (error) { console.error('Service Worker registration failed:', error); } @@ -39,6 +64,16 @@ export default function ServiceWorkerRegistration() { } else { window.addEventListener('load', registerServiceWorker); } + + // Cleanup + return () => { + if (checkInterval) { + clearInterval(checkInterval); + } + document.removeEventListener('visibilitychange', handleVisibilityChange); + window.removeEventListener('focus', handleFocus); + navigator.serviceWorker.removeEventListener('message', handleMessage); + }; } }, []); diff --git a/src/components/UpdateNotification.tsx b/src/components/UpdateNotification.tsx new file mode 100644 index 0000000..30b30e1 --- /dev/null +++ b/src/components/UpdateNotification.tsx @@ -0,0 +1,140 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { RefreshCw, X } from 'lucide-react'; + +export default function UpdateNotification() { + const [showUpdate, setShowUpdate] = useState(false); + const [isUpdating, setIsUpdating] = useState(false); + + useEffect(() => { + if (typeof window === 'undefined' || !('serviceWorker' in navigator)) { + return; + } + + let registration: ServiceWorkerRegistration | null = null; + + const checkForUpdates = async () => { + try { + registration = await navigator.serviceWorker.ready; + + // Listen for service worker updates + registration.addEventListener('updatefound', () => { + const newWorker = registration?.installing; + if (newWorker) { + newWorker.addEventListener('statechange', () => { + if (newWorker.state === 'installed' && navigator.serviceWorker.controller) { + // New service worker available + setShowUpdate(true); + } + }); + } + }); + + // Check for updates every 60 seconds + const checkInterval = setInterval(async () => { + try { + await registration?.update(); + } catch (error) { + console.error('Error checking for updates:', error); + } + }, 60000); + + // Also check on page visibility change + const handleVisibilityChange = () => { + if (!document.hidden && registration) { + registration.update(); + } + }; + document.addEventListener('visibilitychange', handleVisibilityChange); + + // Check on page focus + const handleFocus = () => { + if (registration) { + registration.update(); + } + }; + window.addEventListener('focus', handleFocus); + + return () => { + clearInterval(checkInterval); + document.removeEventListener('visibilitychange', handleVisibilityChange); + window.removeEventListener('focus', handleFocus); + }; + } catch (error) { + console.error('Service Worker registration error:', error); + } + }; + + checkForUpdates(); + + // Check immediately on load + navigator.serviceWorker.getRegistration().then(reg => { + if (reg) { + reg.update(); + } + }); + }, []); + + const handleUpdate = async () => { + setIsUpdating(true); + + if ('serviceWorker' in navigator && navigator.serviceWorker.controller) { + // Send message to service worker to skip waiting + navigator.serviceWorker.controller.postMessage({ type: 'SKIP_WAITING' }); + + // Reload the page after a short delay + setTimeout(() => { + window.location.reload(); + }, 500); + } + }; + + const handleDismiss = () => { + setShowUpdate(false); + }; + + if (!showUpdate) return null; + + return ( +
+
+
+ +
+
+

+ نسخه جدید آماده است +

+

+ برای دریافت آخرین تغییرات، اپ را به‌روزرسانی کنید +

+
+
+ + +
+
+
+ ); +} + diff --git a/src/components/daily-report/SummaryCard.tsx b/src/components/daily-report/SummaryCard.tsx index 3582a13..ce8eac6 100644 --- a/src/components/daily-report/SummaryCard.tsx +++ b/src/components/daily-report/SummaryCard.tsx @@ -25,7 +25,7 @@ export function SummaryCard({ param, currentValue, minValue, maxValue, data }: S case 'soil': return case 'lux': - return + return case 'gas': return default: diff --git a/src/components/daily-report/types.ts b/src/components/daily-report/types.ts index 0aff828..fc2984c 100644 --- a/src/components/daily-report/types.ts +++ b/src/components/daily-report/types.ts @@ -36,8 +36,8 @@ export type GreenhouseAlert = { export const TABS: { value: TabType; label: string }[] = [ { value: 'summary', label: 'خلاصه' }, - { value: 'charts', label: 'گزارش نموداری' }, - { value: 'weather', label: 'وضعیت آب و هوا' }, + { value: 'charts', label: 'نمودار' }, + { value: 'weather', label: 'آب و هوا' }, { value: 'analysis', label: 'تحلیل' }, ] diff --git a/src/lib/version.ts b/src/lib/version.ts new file mode 100644 index 0000000..d8eb35a --- /dev/null +++ b/src/lib/version.ts @@ -0,0 +1,12 @@ +export const APP_VERSION = process.env.NEXT_PUBLIC_APP_VERSION || Date.now().toString(); + +export async function getAppVersion(): Promise { + try { + const response = await fetch('/version.json', { cache: 'no-store' }); + const data = await response.json(); + return data.version; + } catch { + return APP_VERSION; + } +} +