@salesforce/webapp-template-app-react-sample-b2x-experimental 1.96.0 → 1.97.0
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/CHANGELOG.md +8 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/package.json +3 -3
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/WeatherWidget.tsx +221 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/useWeather.ts +73 -32
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Dashboard.tsx +5 -54
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Maintenance.tsx +1 -1
- package/dist/package.json +1 -1
- package/package.json +1 -1
package/dist/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [1.97.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.96.0...v1.97.0) (2026-03-12)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
6
14
|
# [1.96.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.95.0...v1.96.0) (2026-03-12)
|
|
7
15
|
|
|
8
16
|
**Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"graphql:schema": "node scripts/get-graphql-schema.mjs"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@salesforce/sdk-data": "^1.
|
|
19
|
-
"@salesforce/webapp-experimental": "^1.
|
|
18
|
+
"@salesforce/sdk-data": "^1.97.0",
|
|
19
|
+
"@salesforce/webapp-experimental": "^1.97.0",
|
|
20
20
|
"@tailwindcss/vite": "^4.1.17",
|
|
21
21
|
"@tanstack/react-form": "^1.28.5",
|
|
22
22
|
"@types/leaflet": "^1.9.21",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"@graphql-eslint/eslint-plugin": "^4.1.0",
|
|
44
44
|
"@graphql-tools/utils": "^11.0.0",
|
|
45
45
|
"@playwright/test": "^1.49.0",
|
|
46
|
-
"@salesforce/vite-plugin-webapp-experimental": "^1.
|
|
46
|
+
"@salesforce/vite-plugin-webapp-experimental": "^1.97.0",
|
|
47
47
|
"@testing-library/jest-dom": "^6.6.3",
|
|
48
48
|
"@testing-library/react": "^16.1.0",
|
|
49
49
|
"@testing-library/user-event": "^14.5.2",
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { Card } from "@/components/ui/card";
|
|
3
|
+
import { Skeleton } from "@/components/ui/skeleton";
|
|
4
|
+
import { useWeather, type WeatherCurrent, type WeatherHour } from "@/hooks/useWeather";
|
|
5
|
+
import {
|
|
6
|
+
Sun,
|
|
7
|
+
Cloud,
|
|
8
|
+
CloudSun,
|
|
9
|
+
CloudRain,
|
|
10
|
+
CloudSnow,
|
|
11
|
+
CloudLightning,
|
|
12
|
+
CloudFog,
|
|
13
|
+
CloudDrizzle,
|
|
14
|
+
Wind,
|
|
15
|
+
Droplets,
|
|
16
|
+
type LucideIcon,
|
|
17
|
+
} from "lucide-react";
|
|
18
|
+
|
|
19
|
+
type ForecastTab = "today" | "tomorrow" | "next3days";
|
|
20
|
+
|
|
21
|
+
const FORECAST_TABS: { key: ForecastTab; label: string }[] = [
|
|
22
|
+
{ key: "today", label: "Today" },
|
|
23
|
+
{ key: "tomorrow", label: "Tomorrow" },
|
|
24
|
+
{ key: "next3days", label: "Next 3 Days" },
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
function getWeatherIcon(code: number, className = "h-16 w-16 text-gray-400") {
|
|
28
|
+
const props = { className, "aria-hidden": true as const };
|
|
29
|
+
if (code <= 1) return <Sun {...props} />;
|
|
30
|
+
if (code === 2) return <CloudSun {...props} />;
|
|
31
|
+
if (code === 3) return <Cloud {...props} />;
|
|
32
|
+
if (code === 45 || code === 48) return <CloudFog {...props} />;
|
|
33
|
+
if (code >= 51 && code <= 55) return <CloudDrizzle {...props} />;
|
|
34
|
+
if (code >= 61 && code <= 65) return <CloudRain {...props} />;
|
|
35
|
+
if (code >= 71 && code <= 77) return <CloudSnow {...props} />;
|
|
36
|
+
if (code >= 80 && code <= 82) return <CloudRain {...props} />;
|
|
37
|
+
if (code >= 85 && code <= 86) return <CloudSnow {...props} />;
|
|
38
|
+
if (code >= 95) return <CloudLightning {...props} />;
|
|
39
|
+
return <Sun {...props} />;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const Divider = () => <div className="my-5 border-t border-gray-200" />;
|
|
43
|
+
|
|
44
|
+
function WeatherSkeleton() {
|
|
45
|
+
return (
|
|
46
|
+
<div className="mt-5 space-y-5" aria-hidden="true">
|
|
47
|
+
<Skeleton className="h-4 w-32" />
|
|
48
|
+
|
|
49
|
+
<div className="flex items-center justify-between">
|
|
50
|
+
<div className="space-y-2">
|
|
51
|
+
<Skeleton className="h-4 w-20" />
|
|
52
|
+
<Skeleton className="h-14 w-32" />
|
|
53
|
+
</div>
|
|
54
|
+
<Skeleton className="h-20 w-20 rounded-full" />
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<div className="border-t border-gray-200" />
|
|
58
|
+
|
|
59
|
+
<div className="grid grid-cols-3 gap-2">
|
|
60
|
+
{[0, 1, 2].map((i) => (
|
|
61
|
+
<div key={i} className="flex flex-col items-center gap-1.5">
|
|
62
|
+
<Skeleton className="h-5 w-5 rounded-full" />
|
|
63
|
+
<Skeleton className="h-4 w-14" />
|
|
64
|
+
<Skeleton className="h-3 w-10" />
|
|
65
|
+
</div>
|
|
66
|
+
))}
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<div className="border-t border-gray-200" />
|
|
70
|
+
|
|
71
|
+
<div className="flex gap-6">
|
|
72
|
+
<Skeleton className="h-4 w-12" />
|
|
73
|
+
<Skeleton className="h-4 w-16" />
|
|
74
|
+
<Skeleton className="h-4 w-20" />
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<div className="flex gap-3">
|
|
78
|
+
{[0, 1, 2, 3].map((i) => (
|
|
79
|
+
<div
|
|
80
|
+
key={i}
|
|
81
|
+
className="flex w-[70px] flex-col items-center gap-1.5 rounded-2xl border border-gray-100 px-3 py-3"
|
|
82
|
+
>
|
|
83
|
+
<Skeleton className="h-3 w-10" />
|
|
84
|
+
<Skeleton className="h-5 w-5 rounded-full" />
|
|
85
|
+
<Skeleton className="h-4 w-8" />
|
|
86
|
+
</div>
|
|
87
|
+
))}
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function StatItem({
|
|
94
|
+
icon: Icon,
|
|
95
|
+
value,
|
|
96
|
+
label,
|
|
97
|
+
}: {
|
|
98
|
+
icon: LucideIcon;
|
|
99
|
+
value: string;
|
|
100
|
+
label: string;
|
|
101
|
+
}) {
|
|
102
|
+
return (
|
|
103
|
+
<div className="flex flex-col items-center gap-1">
|
|
104
|
+
<Icon className="h-5 w-5 text-gray-400" />
|
|
105
|
+
<p className="text-base font-semibold text-primary">{value}</p>
|
|
106
|
+
<p className="text-xs text-muted-foreground">{label}</p>
|
|
107
|
+
</div>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function CurrentConditions({ current }: { current: WeatherCurrent }) {
|
|
112
|
+
return (
|
|
113
|
+
<>
|
|
114
|
+
<p className="text-sm text-muted-foreground">
|
|
115
|
+
{new Date().toLocaleDateString("en-US", {
|
|
116
|
+
day: "numeric",
|
|
117
|
+
month: "long",
|
|
118
|
+
year: "numeric",
|
|
119
|
+
})}
|
|
120
|
+
</p>
|
|
121
|
+
|
|
122
|
+
<div className="mt-2 flex items-center justify-between">
|
|
123
|
+
<div>
|
|
124
|
+
<p className="text-base text-foreground">{current.description}</p>
|
|
125
|
+
<p className="text-6xl font-bold tracking-tight text-foreground">
|
|
126
|
+
{current.tempF}
|
|
127
|
+
<span className="align-top text-3xl">°</span>
|
|
128
|
+
<span className="text-4xl">F</span>
|
|
129
|
+
</p>
|
|
130
|
+
</div>
|
|
131
|
+
{getWeatherIcon(current.weatherCode, "h-20 w-20 text-gray-400")}
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
<Divider />
|
|
135
|
+
|
|
136
|
+
<div className="grid grid-cols-3 gap-2 text-center">
|
|
137
|
+
<StatItem icon={Wind} value={`${current.windSpeedMph} mph`} label="Wind" />
|
|
138
|
+
<StatItem icon={Droplets} value={`${current.humidity}%`} label="Humidity" />
|
|
139
|
+
<StatItem icon={CloudRain} value={`${current.precipitationProbability}%`} label="Rain" />
|
|
140
|
+
</div>
|
|
141
|
+
</>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function ForecastTabs({
|
|
146
|
+
activeTab,
|
|
147
|
+
onTabChange,
|
|
148
|
+
}: {
|
|
149
|
+
activeTab: ForecastTab;
|
|
150
|
+
onTabChange: (tab: ForecastTab) => void;
|
|
151
|
+
}) {
|
|
152
|
+
return (
|
|
153
|
+
<div className="flex gap-6">
|
|
154
|
+
{FORECAST_TABS.map(({ key, label }) => (
|
|
155
|
+
<button key={key} onClick={() => onTabChange(key)} className="flex flex-col items-center">
|
|
156
|
+
<span
|
|
157
|
+
className={`text-sm font-semibold ${activeTab === key ? "text-foreground" : "text-muted-foreground"}`}
|
|
158
|
+
>
|
|
159
|
+
{label}
|
|
160
|
+
</span>
|
|
161
|
+
{activeTab === key && <span className="mt-1 h-1.5 w-1.5 rounded-full bg-foreground" />}
|
|
162
|
+
</button>
|
|
163
|
+
))}
|
|
164
|
+
</div>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function HourlyForecast({ hours }: { hours: WeatherHour[] }) {
|
|
169
|
+
if (hours.length === 0) return null;
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<div className="mt-4 flex gap-3 overflow-x-auto pb-1">
|
|
173
|
+
{hours.map((h) => (
|
|
174
|
+
<div
|
|
175
|
+
key={h.time}
|
|
176
|
+
className="flex min-w-[70px] flex-col items-center gap-1.5 rounded-2xl border border-gray-100 bg-gray-50/80 px-3 py-3"
|
|
177
|
+
>
|
|
178
|
+
<p className="whitespace-nowrap text-xs text-muted-foreground">{h.time}</p>
|
|
179
|
+
{getWeatherIcon(h.weatherCode, "h-5 w-5 text-gray-500")}
|
|
180
|
+
<p className="text-base font-semibold text-foreground">{h.tempF}°</p>
|
|
181
|
+
</div>
|
|
182
|
+
))}
|
|
183
|
+
</div>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function WeatherWidget() {
|
|
188
|
+
const { data: weather, loading, error } = useWeather();
|
|
189
|
+
const [activeTab, setActiveTab] = useState<ForecastTab>("today");
|
|
190
|
+
|
|
191
|
+
const activeHourly = !weather
|
|
192
|
+
? []
|
|
193
|
+
: activeTab === "tomorrow"
|
|
194
|
+
? weather.tomorrowHourly
|
|
195
|
+
: activeTab === "next3days"
|
|
196
|
+
? weather.next3DaysHourly
|
|
197
|
+
: weather.todayHourly;
|
|
198
|
+
|
|
199
|
+
return (
|
|
200
|
+
<Card className="rounded-2xl border-0 p-6 shadow-md">
|
|
201
|
+
<h2 className="text-lg font-semibold text-primary">Weather</h2>
|
|
202
|
+
|
|
203
|
+
{loading && <WeatherSkeleton />}
|
|
204
|
+
|
|
205
|
+
{error && (
|
|
206
|
+
<p className="mt-4 text-sm text-destructive" role="alert">
|
|
207
|
+
{error}
|
|
208
|
+
</p>
|
|
209
|
+
)}
|
|
210
|
+
|
|
211
|
+
{!loading && !error && weather && (
|
|
212
|
+
<div className="mt-5">
|
|
213
|
+
<CurrentConditions current={weather.current} />
|
|
214
|
+
<Divider />
|
|
215
|
+
<ForecastTabs activeTab={activeTab} onTabChange={setActiveTab} />
|
|
216
|
+
<HourlyForecast hours={activeHourly} />
|
|
217
|
+
</div>
|
|
218
|
+
)}
|
|
219
|
+
</Card>
|
|
220
|
+
);
|
|
221
|
+
}
|
package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/useWeather.ts
CHANGED
|
@@ -49,16 +49,21 @@ export interface WeatherCurrent {
|
|
|
49
49
|
humidity: number;
|
|
50
50
|
windSpeedKmh: number;
|
|
51
51
|
windSpeedMph: number;
|
|
52
|
+
weatherCode: number;
|
|
53
|
+
precipitationProbability: number;
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
export interface WeatherHour {
|
|
55
|
-
time: string;
|
|
57
|
+
time: string;
|
|
56
58
|
tempF: number;
|
|
59
|
+
weatherCode: number;
|
|
57
60
|
}
|
|
58
61
|
|
|
59
62
|
export interface WeatherData {
|
|
60
63
|
current: WeatherCurrent;
|
|
61
|
-
|
|
64
|
+
todayHourly: WeatherHour[];
|
|
65
|
+
tomorrowHourly: WeatherHour[];
|
|
66
|
+
next3DaysHourly: WeatherHour[];
|
|
62
67
|
timezone: string;
|
|
63
68
|
}
|
|
64
69
|
|
|
@@ -70,9 +75,9 @@ async function fetchWeather(lat: number, lng: number): Promise<WeatherData> {
|
|
|
70
75
|
"current",
|
|
71
76
|
"temperature_2m,relative_humidity_2m,weather_code,wind_speed_10m",
|
|
72
77
|
);
|
|
73
|
-
url.searchParams.set("hourly", "temperature_2m");
|
|
78
|
+
url.searchParams.set("hourly", "temperature_2m,weather_code,precipitation_probability");
|
|
74
79
|
url.searchParams.set("timezone", "America/Los_Angeles");
|
|
75
|
-
url.searchParams.set("forecast_days", "
|
|
80
|
+
url.searchParams.set("forecast_days", "4");
|
|
76
81
|
|
|
77
82
|
const res = await fetch(url.toString());
|
|
78
83
|
if (!res.ok) throw new Error(`Weather failed: ${res.status}`);
|
|
@@ -83,7 +88,12 @@ async function fetchWeather(lat: number, lng: number): Promise<WeatherData> {
|
|
|
83
88
|
weather_code?: number;
|
|
84
89
|
wind_speed_10m?: number;
|
|
85
90
|
};
|
|
86
|
-
hourly?: {
|
|
91
|
+
hourly?: {
|
|
92
|
+
time?: string[];
|
|
93
|
+
temperature_2m?: (number | null)[];
|
|
94
|
+
weather_code?: (number | null)[];
|
|
95
|
+
precipitation_probability?: (number | null)[];
|
|
96
|
+
};
|
|
87
97
|
timezone?: string;
|
|
88
98
|
};
|
|
89
99
|
|
|
@@ -94,38 +104,65 @@ async function fetchWeather(lat: number, lng: number): Promise<WeatherData> {
|
|
|
94
104
|
const windMph = Math.round(windKmh * 0.621371 * 10) / 10;
|
|
95
105
|
const code = cur.weather_code ?? 0;
|
|
96
106
|
|
|
97
|
-
const hourly: WeatherHour[] = [];
|
|
98
107
|
const times = data.hourly?.time ?? [];
|
|
99
108
|
const temps = data.hourly?.temperature_2m ?? [];
|
|
109
|
+
const hourlyCodes = data.hourly?.weather_code ?? [];
|
|
110
|
+
const precipProbs = data.hourly?.precipitation_probability ?? [];
|
|
100
111
|
const now = new Date();
|
|
101
|
-
|
|
112
|
+
|
|
113
|
+
let currentPrecipProb = 0;
|
|
114
|
+
for (let i = 0; i < times.length; i++) {
|
|
102
115
|
const t = times[i];
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if (d >= now) {
|
|
107
|
-
hourly.push({
|
|
108
|
-
time: d.toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit", hour12: true }),
|
|
109
|
-
tempF: celsiusToFahrenheit(temp),
|
|
110
|
-
});
|
|
111
|
-
}
|
|
116
|
+
if (t && new Date(t) >= now) {
|
|
117
|
+
currentPrecipProb = precipProbs[i] ?? 0;
|
|
118
|
+
break;
|
|
112
119
|
}
|
|
113
120
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
121
|
+
|
|
122
|
+
const todayDateStr = now.toISOString().split("T")[0];
|
|
123
|
+
const tomorrowDate = new Date(now);
|
|
124
|
+
tomorrowDate.setDate(tomorrowDate.getDate() + 1);
|
|
125
|
+
const tomorrowDateStr = tomorrowDate.toISOString().split("T")[0];
|
|
126
|
+
|
|
127
|
+
const todayHourly: WeatherHour[] = [];
|
|
128
|
+
const tomorrowHourly: WeatherHour[] = [];
|
|
129
|
+
const next3DaysHourly: WeatherHour[] = [];
|
|
130
|
+
|
|
131
|
+
for (let i = 0; i < times.length; i++) {
|
|
132
|
+
const t = times[i];
|
|
133
|
+
const temp = temps[i];
|
|
134
|
+
if (!t || temp == null) continue;
|
|
135
|
+
|
|
136
|
+
const d = new Date(t);
|
|
137
|
+
const dateStr = t.split("T")[0];
|
|
138
|
+
const wc = hourlyCodes[i] ?? 0;
|
|
139
|
+
const langCode = navigator.language ?? "en-US";
|
|
140
|
+
|
|
141
|
+
if (dateStr === todayDateStr && d > now) {
|
|
142
|
+
todayHourly.push({
|
|
143
|
+
time: d.toLocaleTimeString(langCode, { hour: "numeric" }),
|
|
144
|
+
tempF: celsiusToFahrenheit(temp),
|
|
145
|
+
weatherCode: wc,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (dateStr === tomorrowDateStr && d.getHours() % 3 === 0) {
|
|
150
|
+
tomorrowHourly.push({
|
|
151
|
+
time: d.toLocaleTimeString(langCode, { hour: "numeric" }),
|
|
152
|
+
tempF: celsiusToFahrenheit(temp),
|
|
153
|
+
weatherCode: wc,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (dateStr > todayDateStr && d.getHours() % 6 === 0) {
|
|
158
|
+
next3DaysHourly.push({
|
|
159
|
+
time:
|
|
160
|
+
d.toLocaleDateString(langCode, { weekday: "short" }) +
|
|
161
|
+
" " +
|
|
162
|
+
d.toLocaleTimeString(langCode, { hour: "numeric" }),
|
|
163
|
+
tempF: celsiusToFahrenheit(temp),
|
|
164
|
+
weatherCode: wc,
|
|
165
|
+
});
|
|
129
166
|
}
|
|
130
167
|
}
|
|
131
168
|
|
|
@@ -136,8 +173,12 @@ async function fetchWeather(lat: number, lng: number): Promise<WeatherData> {
|
|
|
136
173
|
humidity,
|
|
137
174
|
windSpeedKmh: windKmh,
|
|
138
175
|
windSpeedMph: windMph,
|
|
176
|
+
weatherCode: code,
|
|
177
|
+
precipitationProbability: currentPrecipProb,
|
|
139
178
|
},
|
|
140
|
-
|
|
179
|
+
todayHourly,
|
|
180
|
+
tomorrowHourly,
|
|
181
|
+
next3DaysHourly,
|
|
141
182
|
timezone: data.timezone ?? "America/Los_Angeles",
|
|
142
183
|
};
|
|
143
184
|
}
|
package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Dashboard.tsx
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { Card, CardContent
|
|
1
|
+
import { Card, CardContent } from "@/components/ui/card";
|
|
2
2
|
import { Link } from "react-router";
|
|
3
3
|
import { MaintenanceRequestIcon } from "@/components/MaintenanceRequestIcon";
|
|
4
|
-
import {
|
|
4
|
+
import { WeatherWidget } from "@/components/WeatherWidget";
|
|
5
5
|
import { useMaintenanceRequests } from "@/hooks/useMaintenanceRequests";
|
|
6
6
|
|
|
7
7
|
export default function Dashboard() {
|
|
8
|
-
const { data: weather, loading: weatherLoading, error: weatherError } = useWeather();
|
|
9
8
|
const {
|
|
10
9
|
requests: maintenanceRequests,
|
|
11
10
|
loading: maintenanceLoading,
|
|
@@ -19,7 +18,7 @@ export default function Dashboard() {
|
|
|
19
18
|
<div className="min-w-0 flex-1">
|
|
20
19
|
<Card className="border-gray-200 p-6 shadow-sm">
|
|
21
20
|
<div className="mb-6 flex items-center justify-between">
|
|
22
|
-
<h2 className="text-lg font-semibold
|
|
21
|
+
<h2 className="text-lg font-semibold tracking-wide text-primary">
|
|
23
22
|
Maintenance Requests
|
|
24
23
|
</h2>
|
|
25
24
|
<Link
|
|
@@ -89,57 +88,9 @@ export default function Dashboard() {
|
|
|
89
88
|
</CardContent>
|
|
90
89
|
</Card>
|
|
91
90
|
</div>
|
|
92
|
-
{/* Weather
|
|
91
|
+
{/* Weather */}
|
|
93
92
|
<div className="w-full shrink-0 lg:w-[320px]">
|
|
94
|
-
<
|
|
95
|
-
<CardHeader>
|
|
96
|
-
<CardTitle className="text-primary">Weather</CardTitle>
|
|
97
|
-
<p className="text-sm text-muted-foreground">
|
|
98
|
-
{new Date().toLocaleDateString("en-US", {
|
|
99
|
-
weekday: "short",
|
|
100
|
-
month: "short",
|
|
101
|
-
day: "numeric",
|
|
102
|
-
year: "numeric",
|
|
103
|
-
})}
|
|
104
|
-
</p>
|
|
105
|
-
</CardHeader>
|
|
106
|
-
<CardContent className="space-y-4">
|
|
107
|
-
{weatherLoading && <p className="text-sm text-muted-foreground">Loading weather…</p>}
|
|
108
|
-
{weatherError && (
|
|
109
|
-
<p className="text-sm text-destructive" role="alert">
|
|
110
|
-
{weatherError}
|
|
111
|
-
</p>
|
|
112
|
-
)}
|
|
113
|
-
{!weatherLoading && !weatherError && weather && (
|
|
114
|
-
<>
|
|
115
|
-
<p className="text-base text-foreground">{weather.current.description}</p>
|
|
116
|
-
<p className="text-4xl font-bold text-foreground">{weather.current.tempF}°F</p>
|
|
117
|
-
<div className="flex flex-wrap gap-4 text-sm text-muted-foreground">
|
|
118
|
-
<span>{weather.current.windSpeedMph} mph Wind</span>
|
|
119
|
-
<span>{weather.current.humidity}% Humidity</span>
|
|
120
|
-
</div>
|
|
121
|
-
<div className="flex gap-2 border-b border-border pb-2">
|
|
122
|
-
<span className="border-b-2 border-primary pb-1 text-sm font-semibold text-primary">
|
|
123
|
-
Today
|
|
124
|
-
</span>
|
|
125
|
-
</div>
|
|
126
|
-
{weather.hourly.length > 0 && (
|
|
127
|
-
<div className="flex flex-wrap gap-4">
|
|
128
|
-
{weather.hourly.map((h) => (
|
|
129
|
-
<div
|
|
130
|
-
key={h.time}
|
|
131
|
-
className="min-w-[60px] rounded-xl bg-muted/50 p-2 text-center"
|
|
132
|
-
>
|
|
133
|
-
<p className="text-xs text-muted-foreground">{h.time}</p>
|
|
134
|
-
<p className="text-base font-semibold text-foreground">{h.tempF}°</p>
|
|
135
|
-
</div>
|
|
136
|
-
))}
|
|
137
|
-
</div>
|
|
138
|
-
)}
|
|
139
|
-
</>
|
|
140
|
-
)}
|
|
141
|
-
</CardContent>
|
|
142
|
-
</Card>
|
|
93
|
+
<WeatherWidget />
|
|
143
94
|
</div>
|
|
144
95
|
</div>
|
|
145
96
|
);
|
package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Maintenance.tsx
CHANGED
|
@@ -59,7 +59,7 @@ export default function Maintenance() {
|
|
|
59
59
|
Type__c: type.trim() || undefined,
|
|
60
60
|
Priority__c: priority,
|
|
61
61
|
Status__c: "New",
|
|
62
|
-
Scheduled__c: dateRequested
|
|
62
|
+
Scheduled__c: dateRequested ? new Date(dateRequested).toISOString() : undefined,
|
|
63
63
|
});
|
|
64
64
|
setSubmitSuccess(true);
|
|
65
65
|
setTitle("");
|
package/dist/package.json
CHANGED
package/package.json
CHANGED