File size: 8,225 Bytes
be02369
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
import React, { useState, useEffect, useCallback } from 'react';
import SunCalc from 'suncalc';
import { SunClockIcon, AlertTriangleIcon } from '../components/icons';

type PermissionStatus = 'idle' | 'prompting' | 'granted' | 'denied';

const SunTrackerView: React.FC = () => {
    const [permission, setPermission] = useState<PermissionStatus>('idle');
    const [sunData, setSunData] = useState<any>(null);
    const [compassHeading, setCompassHeading] = useState<number>(0);
    const [error, setError] = useState<string>('');

    const handleOrientation = (event: DeviceOrientationEvent) => {
        // webkitCompassHeading is for iOS
        const heading = (event as any).webkitCompassHeading || (360 - event.alpha!);
        setCompassHeading(heading);
    };

    const requestPermissions = useCallback(async () => {
        setPermission('prompting');

        // Geolocation
        if (!("geolocation" in navigator)) {
            setError("Geolocation is not supported by your browser.");
            setPermission('denied');
            return;
        }

        navigator.geolocation.getCurrentPosition(
            (position) => {
                const { latitude, longitude } = position.coords;
                const now = new Date();
                const times = SunCalc.getTimes(now, latitude, longitude);
                const pos = SunCalc.getPosition(now, latitude, longitude);
                setSunData({ ...times, ...pos });

                // Device Orientation (Compass)
                if (typeof (DeviceOrientationEvent as any).requestPermission === 'function') {
                    (DeviceOrientationEvent as any).requestPermission()
                        .then((response: string) => {
                            if (response === 'granted') {
                                window.addEventListener('deviceorientation', handleOrientation);
                                setPermission('granted');
                            } else {
                                setError("Compass permission denied.");
                                setPermission('denied');
                            }
                        });
                } else {
                     window.addEventListener('deviceorientation', handleOrientation);
                     setPermission('granted');
                }
            },
            (error) => {
                setError("Geolocation permission denied. Please enable it in your browser settings.");
                setPermission('denied');
            }
        );
    }, []);

    useEffect(() => {
        return () => {
            window.removeEventListener('deviceorientation', handleOrientation);
        };
    }, []);

    const renderCompass = () => (
        <div className="relative w-48 h-48 mx-auto">
            <div className="w-full h-full rounded-full bg-stone-100 border-4 border-stone-200 flex items-center justify-center">
                <div className="absolute top-0 w-px h-full bg-stone-300"></div>
                <div className="absolute left-0 h-px w-full bg-stone-300"></div>
                <span className="absolute top-1 text-lg font-bold text-red-600">N</span>
                <span className="absolute bottom-1 text-lg font-bold text-stone-600">S</span>
                <span className="absolute left-2 text-lg font-bold text-stone-600">W</span>
                <span className="absolute right-2 text-lg font-bold text-stone-600">E</span>
            </div>
            {sunData && (
                <div className="absolute inset-0 flex items-center justify-center transform" style={{ transform: `rotate(${-compassHeading}deg)` }}>
                    <div className="w-8 h-8 bg-yellow-400 rounded-full shadow-lg"
                        style={{ transform: `rotate(${sunData.azimuth * 180 / Math.PI}deg) translateY(-50px) rotate(${-sunData.azimuth * 180 / Math.PI}deg) `}}
                        title={`Sun Position: Azimuth ${ (sunData.azimuth * 180 / Math.PI + 180).toFixed(0) }°`}
                        />
                </div>
            )}
             <div className="absolute inset-0 flex items-center justify-center transform transition-transform duration-500" style={{ transform: `rotate(${compassHeading}deg)` }}>
                <div className="w-0 h-0 border-l-8 border-l-transparent border-r-8 border-r-transparent border-b-16 border-b-red-600 transform -translate-y-12"></div>
            </div>
        </div>
    );
    
    const renderSunPath = () => {
        if (!sunData) return null;
        const now = new Date();
        const totalDaylight = sunData.sunset.getTime() - sunData.sunrise.getTime();
        const fromSunrise = now.getTime() - sunData.sunrise.getTime();
        const percentOfDay = Math.max(0, Math.min(1, fromSunrise / totalDaylight));

        return (
            <div className="relative h-24 w-full">
                <svg viewBox="0 0 200 100" className="w-full h-full">
                    <path d="M 10 90 A 90 90 0 0 1 190 90" stroke="#d6d3d1" strokeWidth="4" fill="none" />
                    {percentOfDay > 0 && percentOfDay < 1 && (
                         <circle 
                            cx={10 + percentOfDay * 180} 
                            cy={90 - Math.sin(percentOfDay * Math.PI) * 80}
                            r="8"
                            fill="#facc15"
                            stroke="#ca8a04"
                            strokeWidth="2"
                        />
                    )}
                </svg>
                <div className="absolute top-full w-full flex justify-between text-xs font-semibold text-stone-600">
                    <span>{sunData.sunrise.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}</span>
                    <span>{sunData.sunset.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}</span>
                </div>
            </div>
        );
    }
    
    return (
        <div className="space-y-8 max-w-2xl mx-auto">
            <header className="text-center">
                <h2 className="text-3xl font-bold tracking-tight text-stone-900 sm:text-4xl flex items-center justify-center gap-3">
                    <SunClockIcon className="w-8 h-8 text-yellow-500" />
                    Sun Tracker
                </h2>
                <p className="mt-4 text-lg leading-8 text-stone-600">
                    Find the optimal light for your trees. Use this tool to see the sun's path and current position.
                </p>
            </header>

            <div className="bg-white p-6 rounded-xl shadow-lg border border-stone-200">
                {permission === 'granted' ? (
                    <div className="space-y-6">
                        {renderCompass()}
                        <div className="text-center">
                            <p className="text-lg font-bold text-stone-800">Heading: {compassHeading.toFixed(0)}°</p>
                            <p className="text-sm text-stone-600">Point the top of your device North</p>
                        </div>
                        {renderSunPath()}
                    </div>
                ) : (
                    <div className="text-center py-8">
                        <p className="mb-4 text-stone-700">This tool requires permission to access your device's location and orientation to function.</p>
                        <button onClick={requestPermissions} disabled={permission === 'prompting'} className="bg-yellow-500 text-white font-bold py-3 px-6 rounded-lg hover:bg-yellow-600 transition-colors disabled:bg-stone-400">
                            {permission === 'prompting' ? 'Waiting for Permission...' : 'Activate Sun Tracker'}
                        </button>
                        {error && (
                            <div className="mt-6 p-4 bg-red-50 text-red-700 rounded-lg flex items-center gap-3">
                                <AlertTriangleIcon className="w-6 h-6 flex-shrink-0" />
                                <p className="text-left">{error}</p>
                            </div>
                        )}
                    </div>
                )}
            </div>
        </div>
    );
};

export default SunTrackerView;