adding working IVANS widget
This commit is contained in:
@@ -4,7 +4,7 @@ import { useColorMode } from "@/components/ui/color-mode.jsx";
|
||||
import Plot from 'react-plotly.js';
|
||||
import VdapApi from '../../hooks/useVdapApi.js'; // Adjust path as needed
|
||||
|
||||
export default function ExplosionEvents({ settings }) {
|
||||
export default function ExplosionEvents({ settings, widgetID }) {
|
||||
// Plotly expects 'linear' for normal axes
|
||||
const plotYAxisType = settings?.yAxisScale === 'log' ? 'log' : 'linear';
|
||||
|
||||
|
||||
@@ -1,25 +1,237 @@
|
||||
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';
|
||||
import {Button, Dialog, Flex, Grid, GridItem, Spinner, Text} from "@chakra-ui/react";
|
||||
import {useEffect, useMemo, useState} from 'react';
|
||||
import {useDashboardStore} from "@/store/useDashboardStore.jsx";
|
||||
|
||||
export default function IvansAlerts() {
|
||||
export default function IvansAlerts({ settings, widgetID }) {
|
||||
console.log("IvansAlerts");
|
||||
const [isSummaryOpen, setIsSummaryOpen] = useState(false);
|
||||
const [isFileOpen, setIsFileOpen] = useState(false);
|
||||
const [activeSummaryItem, setActiveSummaryItem] = useState(false);
|
||||
const [activeFileItem, setActiveFileItem] = useState(false);
|
||||
const [activeImageItem, setActiveImageItem] = useState(false);
|
||||
const widgets = useDashboardStore(state => state.widgets);
|
||||
console.log("widgets: ", widgets);
|
||||
const updateSettings = useDashboardStore(state => state.updateWidgetSettings);
|
||||
const isCopy = settings?.copyToClipboard === true; // acts as a failsafe if no default setting is set
|
||||
const sorts = settings?.activeSorts;
|
||||
|
||||
// build url once on mount
|
||||
const seven_day_url = useMemo(() => {
|
||||
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');
|
||||
|
||||
return base_url + '?start_date=' + sevenDaysAgo + '&end_date=' + today;
|
||||
}, []);
|
||||
|
||||
// VdapApi hook only calls api if the url changes
|
||||
const {data: apiD, isLoading, error} = VdapApi(seven_day_url);
|
||||
|
||||
const apiData = [
|
||||
{
|
||||
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: 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: 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"
|
||||
},
|
||||
{
|
||||
name: "275-273030-Mayon-2026-02.pdf",
|
||||
url: "https://vdap.org/ivans/sitrep/275-273030-Mayon-2026-02.pdf",
|
||||
type: "pdf"
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const data = useMemo(() => {
|
||||
if (apiData.length === 0) return [];
|
||||
let sorted = [];
|
||||
|
||||
if (sorts?.volcanoSort && sorts?.timeSort === "asc") {
|
||||
sorted = [...apiData].sort(ascTimeVolcanoSort);
|
||||
} else if (sorts?.volcanoSort && sorts?.timeSort === "desc") {
|
||||
sorted = [...apiData].sort(descTimeVolcanoSort);
|
||||
} else if (sorts?.timeSort === "asc") {
|
||||
sorted = [...apiData].sort(ascTimeSort);
|
||||
} else {
|
||||
sorted = [...apiData].sort(descTimeSort);
|
||||
}
|
||||
|
||||
console.log("sorted data: ", sorted);
|
||||
return sorted;
|
||||
}, [apiData, sorts]);
|
||||
|
||||
useEffect(() => {
|
||||
if(isCopy) {
|
||||
navigator.clipboard.writeText(data)
|
||||
.then(() => {
|
||||
alert('Copied to clipboard!');
|
||||
})
|
||||
.catch((err => {
|
||||
console.error('Failed to copy: ', err);
|
||||
}));
|
||||
updateSettings(widgetID, {copyToClipboard: false});
|
||||
}
|
||||
}, [isCopy]);
|
||||
|
||||
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 (data.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 this week.</Text>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex w="100%" h="100%" direction="column" bg="gray.800" borderRadius="xl" align="center" justify="center" gap={2} p={2} overflow="auto">
|
||||
{/*<Grid w="100%" h="100%" templateColumns="repeat(6, 1fr)" alignItems="center">*/}
|
||||
{/* <GridItem colSpan={1} color="gray.100" fontWeight="bold" pl={4}>Volcano</GridItem>*/}
|
||||
{/* <GridItem colSpan={1} color="gray.100" fontWeight="bold">Report Number</GridItem>*/}
|
||||
{/* <GridItem colSpan={1} color="gray.100" fontWeight="bold" pl={3}>Report Send Time</GridItem>*/}
|
||||
{/* <GridItem colSpan={1} color="gray.100" fontWeight="bold" textAlign="center">Preparer</GridItem>*/}
|
||||
{/* <GridItem colSpan={1} color="gray.100" fontWeight="bold" textAlign="center">Summary</GridItem>*/}
|
||||
{/* <GridItem colSpan={1} color="gray.100" fontWeight="bold" textAlign="center">Attachments</GridItem>*/}
|
||||
{/*</Grid>*/}
|
||||
{data.map((item, index) => {
|
||||
const context_string = item.volcano_name + ' • ' + item.send_time;
|
||||
return (
|
||||
<Grid w="100%" h="100%" templateColumns="repeat(8, 1fr)" key={index} borderRadius="lg" bg="gray.700" alignItems="center" justifyContent="center">
|
||||
<GridItem colSpan={2} color="gray.100" pl={4}>{item.volcano_name}</GridItem>
|
||||
<GridItem colSpan={1} color="gray.100">{item.report_num}</GridItem>
|
||||
<GridItem colSpan={2} color="gray.100">{item.send_time}</GridItem>
|
||||
<GridItem colSpan={1} color="gray.100">{item.preparer}</GridItem>
|
||||
<GridItem colSpan={1}>
|
||||
<>
|
||||
<Button w="7rem" h={0} mb={1} onClick={() => setActiveSummaryItem(item)} color="purple.300" variant="plain" textDecor="underline">View Summary</Button>
|
||||
<SummaryDialog
|
||||
open={!!activeSummaryItem}
|
||||
onOpenChange={() => setActiveSummaryItem(null)}
|
||||
item={activeSummaryItem}
|
||||
context={context_string}
|
||||
/>
|
||||
</>
|
||||
</GridItem>
|
||||
<GridItem colSpan={1}>
|
||||
<Flex direction="column" justify="center" alignItems="center" gap={0}>
|
||||
{item.files.length === 0 ? (
|
||||
<Text color="gray.600" fontSize="sm">No Attachments</Text>
|
||||
):(
|
||||
item.files.map((file, index) => {
|
||||
if (file.type === "pdf") return (
|
||||
<div key={index}>
|
||||
<Button w="7rem" h={0} mb={1} onClick={() => setActiveFileItem(file)} color="purple.300" variant="plain" textDecor="underline">View PDF</Button>
|
||||
<FileDialog
|
||||
open={!!activeFileItem}
|
||||
onOpenChange={() => setActiveFileItem(null)}
|
||||
item={activeFileItem}
|
||||
context={context_string}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
else if (file.type === "image") return (
|
||||
<div key={index}>
|
||||
<Button w="7rem" onClick={() => setActiveImageItem(file)} color="purple.300" variant="plain" textDecor="underline">View Image</Button>
|
||||
<ImageDialog
|
||||
open={!!activeImageItem}
|
||||
onOpenChange={() => setActiveImageItem(null)}
|
||||
item={activeImageItem}
|
||||
context={context_string}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
else if (file.type === "download") return (
|
||||
<Button key={index} w="7rem" onClick={() => downloadReport(file.url, file.name) } color="purple.300" variant="plain" textDecor="underline">Download</Button>
|
||||
)
|
||||
})
|
||||
)}
|
||||
</Flex>
|
||||
</GridItem>
|
||||
</Grid>
|
||||
)
|
||||
})}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
function SummaryDialog({ open, onOpenChange, item }) {
|
||||
return (
|
||||
<Dialog.Root open={open} onOpenChange={onOpenChange}>
|
||||
<Dialog.Root lazyMount open={open} onOpenChange={onOpenChange}>
|
||||
<Dialog.Backdrop />
|
||||
<Dialog.Positioner>
|
||||
<Dialog.Content>
|
||||
<Dialog.Content w="100vw" h="40vh">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>Summary</Dialog.Title>
|
||||
<Dialog.Title w="100%">
|
||||
<Flex w="100%" h="100%" direction="column" textAlign="left">
|
||||
<Flex direction="row" justify="space-between" align="center">
|
||||
<Text fontSize="md">{item?.volcano_name}</Text>
|
||||
<Text bg="gray.800" fontSize="xs" borderRadius="sm" color="gray.500" pl={2} pr={2}>Preparer: {item?.preparer}</Text>
|
||||
</Flex>
|
||||
<Text fontSize="xs" color="blue.400">{item?.send_time}</Text>
|
||||
</Flex>
|
||||
</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
|
||||
<Dialog.Body>
|
||||
<Text>{item.summary}</Text>
|
||||
<Dialog.Body bg="gray.800" overflow="auto">
|
||||
<Text textAlign="left">{item?.summary}</Text>
|
||||
</Dialog.Body>
|
||||
|
||||
<Dialog.Footer>
|
||||
@@ -36,33 +248,31 @@ export default function IvansAlerts() {
|
||||
|
||||
function FileDialog({ open, onOpenChange, item, context }) {
|
||||
return (
|
||||
<Dialog.Root open={open} onOpenChange={onOpenChange}>
|
||||
<Dialog.Root lazyMount open={open} onOpenChange={onOpenChange}>
|
||||
<Dialog.Backdrop />
|
||||
<Dialog.Positioner>
|
||||
<Dialog.Content>
|
||||
<Dialog.Content w="100vw" h="75vh">
|
||||
<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>
|
||||
<Flex height="100%" width="100%" direction="row" align="center" justifyContent="space-between">
|
||||
<Flex direction="column" textAlign="left">
|
||||
<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>
|
||||
<Text bg="gray.800" p="1" borderRadius="sm">{(item?.type || '').toUpperCase()}</Text>
|
||||
</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}>
|
||||
<Dialog.Body p={0}>
|
||||
<Flex bg="gray.800" w="100%" h="100%" alignItems="center" justifyContent="center">
|
||||
<object data={`${item?.url}#view=FitH`} type="application/pdf" style={{ width: '100%', height: '100%', display: 'block' }}>
|
||||
<Flex w="100%" h="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">
|
||||
<a href={item?.url} target="_blank">
|
||||
Download PDF
|
||||
</a>
|
||||
</Flex>
|
||||
</object>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Dialog.Body>
|
||||
|
||||
<Dialog.Footer>
|
||||
@@ -77,88 +287,68 @@ export default function IvansAlerts() {
|
||||
)
|
||||
}
|
||||
|
||||
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);
|
||||
function ImageDialog({ open, onOpenChange, item, context }) {
|
||||
return (
|
||||
<Dialog.Root lazyMount open={open} onOpenChange={onOpenChange}>
|
||||
<Dialog.Backdrop />
|
||||
<Dialog.Positioner>
|
||||
<Dialog.Content w="100vw" h="40vh">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{item?.name}</Dialog.Title>
|
||||
<Text color="gray.200">{context}</Text>
|
||||
</Dialog.Header>
|
||||
|
||||
sevenDaysAgo = sevenDaysAgo.toLocaleDateString('en-CA');
|
||||
today = today.toLocaleDateString('en-CA');
|
||||
<Dialog.Body bg="gray.800" overflow="auto">
|
||||
<img src={item?.url} alt={item?.name} />
|
||||
</Dialog.Body>
|
||||
|
||||
const url_with_dates = base_url + '?start_date=' + sevenDaysAgo + '&end_date=' + today;
|
||||
return url_with_dates;
|
||||
<Dialog.Footer>
|
||||
<Button variant="outline" onClick={() => onOpenChange({ open: false })}>
|
||||
Close
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
<Dialog.CloseTrigger />
|
||||
</Dialog.Content>
|
||||
</Dialog.Positioner>
|
||||
</Dialog.Root>
|
||||
)
|
||||
}
|
||||
|
||||
const seven_day_url = get_7_day_range_url();
|
||||
const downloadReport = ( url, name ) => {
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = name;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
|
||||
const {data: apiData, isLoading, error} = VdapApi(seven_day_url);
|
||||
function ascTimeVolcanoSort(a, b) {
|
||||
if (a.volcano_name < b.volcano_name) return -1
|
||||
else if (a.volcano_name > b.volcano_name) return 1
|
||||
else return ascTimeSort(a, b);
|
||||
}
|
||||
|
||||
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>
|
||||
);
|
||||
function descTimeVolcanoSort(a, b) {
|
||||
if (a.volcano_name < b.volcano_name) return -1
|
||||
else if (a.volcano_name > b.volcano_name) return 1
|
||||
else return descTimeSort(a, b);
|
||||
}
|
||||
|
||||
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>
|
||||
);
|
||||
function ascTimeSort(a, b) {
|
||||
const aTime = new Date(a.send_time);
|
||||
const bTime = new Date(b.send_time);
|
||||
|
||||
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>
|
||||
);
|
||||
if (aTime > bTime) return 1;
|
||||
if (aTime < bTime) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
function descTimeSort(a, b) {
|
||||
const aTime = new Date(a.send_time);
|
||||
const bTime = new Date(b.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>
|
||||
);
|
||||
if (aTime > bTime) return -1;
|
||||
if (aTime < bTime) return 1;
|
||||
return 0;
|
||||
}
|
||||
@@ -67,6 +67,11 @@ const mockData = [
|
||||
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"
|
||||
},
|
||||
{
|
||||
name: "275-273030-Mayon-2026-02.pdf",
|
||||
url: "https://vdap.org/ivans/sitrep/275-273030-Mayon-2026-02.pdf",
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Box, Flex, Spinner, Text } from '@chakra-ui/react';
|
||||
import Plot from 'react-plotly.js';
|
||||
import VdapApi from '../../hooks/useVdapApi.js'; // Adjust path as needed
|
||||
|
||||
export default function VaaActivity7Day({ settings }) {
|
||||
export default function VaaActivity7Day({ settings, widgetID }) {
|
||||
console.log("3. Chart received settings:", settings);
|
||||
// Plotly expects 'linear' for normal axes
|
||||
const plotYAxisType = settings?.yAxisScale === 'symlog' ? 'symlog' : 'linear';
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import {Button, Flex, Switch, Text} from "@chakra-ui/react";
|
||||
import {useDashboardStore} from "../../../store/useDashboardStore.jsx";
|
||||
|
||||
export default function IvansAlertsSettings({ widgetId, currentSettings }) {
|
||||
const updateSettings = useDashboardStore(state => state.updateWidgetSettings);
|
||||
|
||||
const handleCopyToClipboard = () => {
|
||||
updateSettings(widgetId, {copyToClipboard: true});
|
||||
}
|
||||
const handleVolcanoSort = (details) => {
|
||||
const isSort = currentSettings?.activeSorts?.volcanoSort === true;
|
||||
updateSettings(widgetId, { activeSorts: { ...currentSettings?.activeSorts, volcanoSort: !isSort }});
|
||||
}
|
||||
const handleTimeSort = (details) => {
|
||||
const sort = currentSettings?.activeSorts?.timeSort === "desc" ? "asc" : "desc";
|
||||
updateSettings(widgetId, { activeSorts: { ...currentSettings?.activeSorts, timeSort: sort }});
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex flexDirection="column" justifyContent="space-between" gap={2}>
|
||||
<Button onClick={handleCopyToClipboard}>Copy All IVANS</Button>
|
||||
<Switch.Root
|
||||
checked={currentSettings?.activeSorts?.timeSort === "asc"}
|
||||
onCheckedChange={handleTimeSort}
|
||||
colorPalette="blue"
|
||||
>
|
||||
<Switch.HiddenInput />
|
||||
<Switch.Control>
|
||||
<Switch.Thumb />
|
||||
</Switch.Control>
|
||||
<Switch.Label>Sort Oldest to Newest</Switch.Label>
|
||||
</Switch.Root>
|
||||
<Switch.Root
|
||||
checked={currentSettings?.activeSorts?.volcanoSort}
|
||||
onCheckedChange={handleVolcanoSort}
|
||||
colorPalette="blue"
|
||||
>
|
||||
<Switch.HiddenInput />
|
||||
<Switch.Control>
|
||||
<Switch.Thumb />
|
||||
</Switch.Control>
|
||||
<Switch.Label>Sort by Volcano</Switch.Label>
|
||||
</Switch.Root>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
@@ -41,10 +41,17 @@ export const WIDGET_REGISTRY = {
|
||||
name: "IVANS",
|
||||
category: "Communications",
|
||||
description: "Feed of recent IVANS reports and reviewer statuses.",
|
||||
component: lazy(() => import('../components/widgets/IvansAlerts.jsx')),
|
||||
component: lazy(() => import('../components/Widgets/IvansAlerts.jsx')),
|
||||
settingsComponent: lazy(() => import('../components/Widgets/settings/IvansAlertsSettings.jsx')),
|
||||
defaultSettings: {
|
||||
copyToClipboard: false,
|
||||
activeSorts: {
|
||||
timeSort: "desc",
|
||||
volcanoSort: false
|
||||
}
|
||||
},
|
||||
allowedSizes: {
|
||||
small: { w: 3, h: 12 },
|
||||
medium: { w: 6, h: 24 }
|
||||
small: { w: 6, h: 9 }
|
||||
}
|
||||
},
|
||||
|
||||
@@ -89,8 +96,7 @@ export const WIDGET_REGISTRY = {
|
||||
"llm_al_changes_quad_plot": {
|
||||
name: "Alert Level Changes Quad Plot",
|
||||
category: "Computer Science",
|
||||
description: "Percentage of True-Positive, True-Negative, False-Positive, and False-Negative changes in all " +
|
||||
"alert levels.",
|
||||
description: "Percentage of TP, TN, FP, and FN changes in viable alert levels.",
|
||||
component: lazy(() => import('../components/Widgets/LlmAlChangesQuadPlot.jsx')),
|
||||
allowedSizes: {
|
||||
small: { w: 3, h: 12 }
|
||||
|
||||
@@ -121,7 +121,8 @@ export default function MyDashboard() {
|
||||
{/* THE CHART CANVAS */}
|
||||
<Box flex="1" overflow="hidden" position="relative" p={1}>
|
||||
{/* Pass the settings object into the actual chart component */}
|
||||
<Component settings={widget.settings || {}} />
|
||||
{/* Pass widget ID to allow updating settings in widget component rather than just in settings component */}
|
||||
<Component settings={widget.settings || {}} widgetID={widget.widgetID || {}}/>
|
||||
</Box>
|
||||
|
||||
</Flex>
|
||||
|
||||
@@ -44,7 +44,7 @@ export const useDashboardStore = create((set, get) => ({
|
||||
},
|
||||
|
||||
updateWidgetSettings: (id, newSettings) => set((state) => {
|
||||
console.log("2. Zustand updating widget:", id, "with:", newSettings);
|
||||
// console.log("2. Zustand updating widget:", id, "with:", newSettings);
|
||||
return {
|
||||
widgets: state.widgets.map(widget =>
|
||||
widget.id === id
|
||||
@@ -82,12 +82,13 @@ export const useDashboardStore = create((set, get) => ({
|
||||
id: uuid(),
|
||||
type: widgetId,
|
||||
size: sizeKey,
|
||||
settings: registryEntry.defaultSettings,
|
||||
x: startX,
|
||||
y: startY,
|
||||
w: dimensions.w,
|
||||
h: dimensions.h
|
||||
};
|
||||
|
||||
console.log("newWidget: ", newWidget);
|
||||
set((state) => ({widgets: [...state.widgets, newWidget]}));
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user