added a basic ivans widget, still needs ui refactoring

This commit is contained in:
2026-07-22 16:29:17 -07:00
parent 35eea905a3
commit bb1e55d796
3 changed files with 287 additions and 0 deletions
@@ -0,0 +1,164 @@
import VdapApi from '../../hooks/useVdapApi.js';
import {Flex, Spinner, Text, Button, Dialog, Box} from "@chakra-ui/react";
import * as url from "node:url"; // Adjust path as needed
import { useState } from 'react';
export default function IvansAlerts() {
console.log("IvansAlerts");
const [isSummaryOpen, setIsSummaryOpen] = useState(false);
const [isFileOpen, setIsFileOpen] = useState(false);
function SummaryDialog({ open, onOpenChange, item }) {
return (
<Dialog.Root open={open} onOpenChange={onOpenChange}>
<Dialog.Backdrop />
<Dialog.Positioner>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>Summary</Dialog.Title>
</Dialog.Header>
<Dialog.Body>
<Text>{item.summary}</Text>
</Dialog.Body>
<Dialog.Footer>
<Button variant="outline" onClick={() => onOpenChange({ open: false })}>
Close
</Button>
</Dialog.Footer>
<Dialog.CloseTrigger />
</Dialog.Content>
</Dialog.Positioner>
</Dialog.Root>
)
}
function FileDialog({ open, onOpenChange, item, context }) {
return (
<Dialog.Root open={open} onOpenChange={onOpenChange}>
<Dialog.Backdrop />
<Dialog.Positioner>
<Dialog.Content>
<Dialog.Header>
<Flex width="100%" direction="row" align="center" justifyContent="space-between">
<Flex direction="column">
<Dialog.Title>{item.name ? item.name : 'PDF Attachment'}</Dialog.Title>
<Text color="gray.200">{context}</Text>
</Flex>
<Flex>
<Text bg="gray.800" p="1" borderRadius="sm">{item.type}</Text>
</Flex>
</Flex>
</Dialog.Header>
<Dialog.Body h="75vh" p={0}>
<Box bg="gray.900" w="100%" h="100%" display="flex" alignItems="center" justifyContent="center">
<object display="block" data={`${item.url}#view=FitH`} type="application/pdf">
<Flex height="100%" color="gray.400" p={4}>
<Text mb={2} color="fg.muted">Your browser cannot display this PDF directly.</Text>
<a href={item.url} target="_blank">
Download PDF
</a>
</Flex>
</object>
</Box>
</Dialog.Body>
<Dialog.Footer>
<Button variant="outline" onClick={() => onOpenChange({ open: false })}>
Close
</Button>
</Dialog.Footer>
<Dialog.CloseTrigger />
</Dialog.Content>
</Dialog.Positioner>
</Dialog.Root>
)
}
const get_7_day_range_url = () => {
const base_url = '/api/v2/comm_coor_ivans'
let today = new Date();
let sevenDaysAgo = new Date(today);
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
sevenDaysAgo = sevenDaysAgo.toLocaleDateString('en-CA');
today = today.toLocaleDateString('en-CA');
const url_with_dates = base_url + '?start_date=' + sevenDaysAgo + '&end_date=' + today;
return url_with_dates;
}
const seven_day_url = get_7_day_range_url();
const {data: apiData, isLoading, error} = VdapApi(seven_day_url);
if (isLoading) return (
<Flex w="100%" h="100%" bg="gray.800" borderRadius="md" align="center" justify="center">
<Spinner size="xl" color="blue.400" thickness="4px"/>
</Flex>
);
if (error) return (
<Flex w="100%" h="100%" bg="gray.800" borderRadius="md" align="center" justify="center" p={4}>
<Text color="red.400">{error}</Text>
</Flex>
);
if (apiData.length === 0) return (
<Flex height="100%" width="100%" align="center" justify="center" bg="gray.800" borderRadius="md">
<Text color="gray.400">No flight level data available.</Text>
</Flex>
);
return (
<Flex w="100%" h="100%" direction="column" bg="gray.800" borderRadius="md" align="center" justify="center" p={4}>
<Flex direction="row" gap={4}>
<Text color="gray.400">Volcano</Text>
<Text color="gray.400">Report Number</Text>
<Text color="gray.400">Report Send Time</Text>
<Text color="gray.400">Preparer</Text>
<Text color="gray.400">Summary</Text>
<Text color="gray.400">Attachments</Text>
</Flex>
{apiData.map((item, index) => {
const context_str = item.volcano_name + " " + "•" + " " + item.send_time;
return (
<Flex direction="row" key={index} gap={4}>
<Text color="gray.400">{item.volcano_name}</Text>
<Text color="gray.400">{item.report_num}</Text>
<Text color="gray.400">{item.send_time}</Text>
<Text color="gray.400">{item.preparer}</Text>
<Button onClick={() => setIsSummaryOpen(true)} color="gray.400">View</Button>
<SummaryDialog
open={isSummaryOpen}
onOpenChange={(e) => setIsSummaryOpen(e.open)}
item={item}
/>
{item.files.map((item, index) => {
const $file_name = item.name;
const $url = item.url;
const $type = item.type;
if ($type === "pdf") return (
<>
<Button onClick={() => setIsFileOpen(true)}>{$type}</Button>
<FileDialog
open={isFileOpen}
onOpenChange={(e) => setIsFileOpen(e.open)}
item={item}
context={context_str}
/>
</>
)
})}
</Flex>
)
})}
<Flex>
</Flex>
</Flex>
);
}
@@ -0,0 +1,122 @@
import React from 'react';
import { Box } from '@chakra-ui/react';
import { http, HttpResponse } from 'msw'; // Import MSW
import IvansAlerts from './IvansAlerts';
export default {
title: 'Dashboard Widgets/IVANS Activity',
component: IvansAlerts,
decorators: [
(Story) => (
<Box
w="100%" maxW="1800px" h="400px"
border="2px dashed" borderColor="gray.600" p={2}
resize="both" overflow="auto"
>
<Story />
</Box>
),
],
};
const mockData = [
{
id: 279,
volcano_number: 250030,
report_num: "2026-01",
report_date: "2026-05-15T05:11:53Z",
preparer: "charpel",
reviewer: "jlwnstrn",
send_time: "2026-05-15T05:12:01Z",
volcano_name: "Central Bismarck Sea",
summary: "A M5.4 earthquake occurred on 14 May 2026 at 17:13 (UTC) during the ongoing eruption of the submarine Central Bismarck Sea volcano that began 8 May 2026. The volcano's current activity does not threaten any populated areas, but today’s earthquake indicates that eruptive activity will continue or increase.",
files: [
{
name: "279-250030-Central Bismarck Sea-2026-01.pdf",
url: "https://vdap.org/ivans/sitrep/279-250030-Central Bismarck Sea-2026-01.pdf",
type: "pdf"
}
]
},
{
id: 277,
volcano_number: 268010,
report_num: "2026-01",
report_date: "2026-05-08T22:05:44Z",
preparer: "hwright",
reviewer: "dramsey",
send_time: "2026-05-08T22:05:52Z",
volcano_name: "Dukono",
summary: "At 07:41 local time on 8 May 2026, an explosion of persistently active Dukono volcano occurred, producing an ash plume that rose over 15 km (52,000 feet) above sea level and ejecting ballistic blocks of lava beyond the crater rim. The eruption killed three tourists who were hiking within the official exclusion zone; other hikers were rescued and returned to safety.",
files: [
{
name: "277-268010-Dukono-2026-01.pdf",
url: "https://vdap.org/ivans/sitrep/277-268010-Dukono-2026-01.pdf",
type: "pdf"
}
]
},
{
id: 275,
volcano_number: 273030,
report_num: "2026-02",
report_date: "2026-05-03T05:28:12Z",
preparer: "dramsey",
reviewer: "hwright",
send_time: "2026-05-03T05:28:20Z",
volcano_name: "Mayon",
summary: "At around 5:30 PM local time on May 2, 2026, a pyroclastic flow (cloud of searing ash and rock particles) from erupting Mayon Volcano traveled around 4 km (~2.5 miles) down the southwestern slope of the volcano and produced an ash plume that drifted to the west-southwest. At least 52 villages in various towns in Albay province were heavily affected by thick ash fall, reducing visibility to near zero and making travel extremely difficult. There were no reports of casualties and the volcano alert level remained at Level 3 (on a 0-5 level scale).",
files: [
{
name: "275-273030-Mayon-2026-02.pdf",
url: "https://vdap.org/ivans/sitrep/275-273030-Mayon-2026-02.pdf",
type: "pdf"
}
]
}
];
// Scenario LIVE: call our API for real
export const LivePopulated = {};
// Scenario 1: Successful API Call
export const Populated = {
parameters: {
msw: {
handlers: [
// Intercept the exact URL your component is trying to fetch
http.get('*/api/v2/comm_coor_ivans?start_date=2026-01-01&end_date=2026-07-22', () => {
// Return a 200 OK with the mock JSON
return HttpResponse.json(mockData);
}),
],
},
},
};
// Scenario 2: The 500 Server Crash
export const ServerCrash = {
parameters: {
msw: {
handlers: [
http.get('*/api/v2/comm_coor_ivans?start_date=2026-01-01&end_date=2026-07-22', () => {
// Return a 500 Error
return new HttpResponse(null, { status: 500 });
}),
],
},
},
};
// Scenario 3: Empty State (API works, but no data returned)
export const EmptyData = {
parameters: {
msw: {
handlers: [
http.get('*/api/v2/comm_coor_ivans?start_date=2026-01-01&end_date=2026-07-22', () => {
return HttpResponse.json([]);
}),
],
},
},
};