Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/lib/components/UI/dashboard/AllDevices.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
enableDragAndDrop = false
} = $props<{
locations: LocationWithDevices[];
deviceActiveStatus: Record<string, boolean | null>;
deviceActiveStatus: Record<string, boolean | null | undefined>;
onDeviceReorder?: (locationId: number, newDevices: DeviceWithSensorData[]) => void;
enableDragAndDrop?: boolean;
}>();
Expand Down Expand Up @@ -107,8 +107,8 @@
</div>
{:else}
{#each filteredLocations as location (location.location_id)}
{@const hasNullStatus = (location.cw_devices ?? []).some(
(d: DeviceWithSensorData) => deviceActiveStatus[d.dev_eui] === null
{@const hasLoadingStatus = (location.cw_devices ?? []).some(
(d: DeviceWithSensorData) => deviceActiveStatus[d.dev_eui] === undefined
)}
{@const activeDevices = (location.cw_devices ?? []).filter((d: DeviceWithSensorData) =>
isDeviceActive(d, deviceActiveStatus)
Expand All @@ -123,7 +123,7 @@
{activeDevices}
{allActive}
{allInactive}
loading={hasNullStatus}
loading={hasLoadingStatus}
>
{#snippet content()}
{@const locationDevices = location.cw_devices ?? []}
Expand Down
28 changes: 14 additions & 14 deletions src/lib/components/UI/dashboard/DataRowItem.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
} = $props<{
device: DeviceWithLatestData;
location?: Location;
isActive?: boolean;
isActive?: boolean | null | undefined;
detailHref?: string;
children?: any; // snippet passed by parent
onDragStart?: (event: DragEvent, index: number) => void;
Expand All @@ -51,17 +51,17 @@
dragEnabled?: boolean;
}>();

type DeviceStatus = 'loading' | 'active' | 'inactive' | 'not-applicable';

const deviceStatus = $derived<DeviceStatus>(() => {
if (externalIsActive === undefined) return 'loading';
if (externalIsActive === null) return 'not-applicable';
return externalIsActive ? 'active' : 'inactive';
});

let isActive = $derived(
externalIsActive !== undefined
? externalIsActive === null
? null
: Boolean(externalIsActive)
: null
deviceStatus === 'active' ? true : deviceStatus === 'inactive' ? false : null
);
let statusConfirmed = $state(false);
$effect(() => {
if (externalIsActive !== undefined && externalIsActive !== null) statusConfirmed = true;
});

let primaryDataKey = $derived(device.cw_device_type.primary_data_v2);
let secondaryDataKey = $derived(device.cw_device_type.secondary_data_v2);
Expand Down Expand Up @@ -112,10 +112,10 @@
>
<div
class="absolute top-0 bottom-0 left-0 my-1 w-1.5 rounded-full opacity-70 transition-all duration-200"
class:bg-blue-300={!statusConfirmed || isActive === null}
class:bg-blue-400={statusConfirmed && !device.latestData?.created_at}
class:bg-green-500={statusConfirmed && isActive}
class:bg-red-500={statusConfirmed && !isActive && device.latestData?.created_at}
class:bg-yellow-400={deviceStatus === 'loading'}
class:bg-green-500={deviceStatus === 'active'}
class:bg-red-500={deviceStatus === 'inactive'}
class:bg-gray-400={deviceStatus === 'not-applicable'}
class:cursor-grab={dragEnabled}
class:cursor-grabbing={isDragging}
class:hover:opacity-100={dragEnabled}
Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/UI/dashboard/DeviceCard.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
onDrop
} = $props<{
device: DeviceWithLatestData;
isActive: boolean | null;
isActive: boolean | null | undefined;
locationId: number;
dragEnabled?: boolean;
dragIndex?: number;
Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/UI/dashboard/DeviceCards.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
} = $props<{
devices?: DeviceWithSensorData[];
viewType?: string;
deviceActiveStatus?: Record<string, boolean>;
deviceActiveStatus?: Record<string, boolean | null | undefined>;
selectedDevice?: string | null;
onDevicesReorder?: ((newDevices: DeviceWithSensorData[]) => void) | undefined;
enableDragAndDrop?: boolean;
Expand Down
27 changes: 18 additions & 9 deletions src/lib/components/UI/dashboard/DeviceDataList.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
};
}

let { device, isActive = false } = $props<{ device: DeviceWithLatestData; isActive?: boolean }>();
let { device, isActive = undefined } = $props<{
device: DeviceWithLatestData;
isActive?: boolean | null | undefined;
}>();

// Log the active status for debugging
$effect(() => {
Expand Down Expand Up @@ -76,14 +79,20 @@
<span class="flex-grow"></span>

{#if dataPointKey === 'created_at'}
<p class="flex flex-row align-bottom text-xs text-gray-600 dark:text-gray-400">
<Duration
start={device.last_data_updated_at ?? device.latestData.created_at}
totalUnits={2}
minUnits={DurationUnits.Second}
/>
&nbsp;{$_('ago')}
</p>
{#if device.last_data_updated_at}
<p class="flex flex-row align-bottom text-xs text-gray-600 dark:text-gray-400">
<Duration
start={device.last_data_updated_at}
totalUnits={2}
minUnits={DurationUnits.Second}
/>
&nbsp;{$_('ago')}
</p>
{:else}
<p class="text-xs text-gray-500 italic dark:text-gray-400">
{$_('Not applicable')}
</p>
{/if}
{:else}
<div class="text-right">
<span class="text-lg font-bold text-gray-900 dark:text-white">
Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/UI/dashboard/LocationsPanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// Props
export let locations: LocationWithCount[] = [];
export let selectedLocation: number | null = null;
export let deviceActiveStatus: Record<string, boolean> = {};
export let deviceActiveStatus: Record<string, boolean | null | undefined> = {};
export let search: string = '';

// Function to handle location selection
Expand Down
31 changes: 14 additions & 17 deletions src/lib/components/dashboard/LocationSidebar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
dashboardViewType: string;
dashboardSortType: string;
collapsed: boolean;
deviceActiveStatus?: Record<string, boolean | null>;
deviceActiveStatus?: Record<string, boolean | null | undefined>;
onSelectLocation: (locationId: number | null) => void;
onToggleCollapse: () => void;
onsearch: (search: string) => void;
Expand Down Expand Up @@ -73,7 +73,7 @@
dashboardViewType?: 'grid' | 'list' | string;
dashboardSortType?: 'name' | 'status' | string;
collapsed?: boolean;
deviceActiveStatus?: Record<string, boolean | null>;
deviceActiveStatus?: Record<string, boolean | null | undefined>;
onSelectLocation?: (locationId: number | null) => void;
onToggleCollapse?: () => void;
onsearch?: (value: string) => void;
Expand Down Expand Up @@ -156,32 +156,29 @@
}

// Check if any devices have null status (loading state)
const hasNullStatus = location.cw_devices.some(
(device) => device.dev_eui && deviceActiveStatus[device.dev_eui] === null
const hasLoadingStatus = location.cw_devices.some(
(device) => device.dev_eui && deviceActiveStatus[device.dev_eui] === undefined
);

// If any device has null status, show loading
if (hasNullStatus) {
if (hasLoadingStatus) {
return { statusClass: 'status-loading', icon: mdiClockOutline };
}

// Convert deviceActiveStatus to a Record<string, boolean> for the utility function
// This matches how AllDevices.svelte handles it for the dashboard cards
const activeStatusMap: Record<string, boolean> = {};
for (const [key, value] of Object.entries(deviceActiveStatus)) {
activeStatusMap[key] = value === true; // Only true values are considered active
}

// Filter active devices using the same logic as the dashboard
const activeDevices = location.cw_devices.filter(
(device) => device.dev_eui && activeStatusMap[device.dev_eui] === true
(device) => device.dev_eui && deviceActiveStatus[device.dev_eui] === true
);

const inactiveDevices = location.cw_devices.filter(
(device) => device.dev_eui && deviceActiveStatus[device.dev_eui] === false
);

// Calculate status flags using the same logic as the dashboard
const allActive =
location.cw_devices.length > 0 && activeDevices.length === location.cw_devices.length;

const allInactive = location.cw_devices.length > 0 && activeDevices.length === 0;
const allInactive =
location.cw_devices.length > 0 &&
activeDevices.length === 0 &&
inactiveDevices.length === location.cw_devices.length;

// Return status based on the same conditions as the dashboard
if (allActive) {
Expand Down
37 changes: 36 additions & 1 deletion src/lib/stores/LocationsStore.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,33 @@ const selectedLocation = $derived(
locations.find((loc) => loc.location_id === selectedLocationId) || null
);

// Initialize locations from preloaded data
function initialize(
initialLocations: (Location & { deviceCount?: number; cw_devices?: DeviceWithSensorData[] })[]
) {
loadingLocations = false;
locationError = null;

locations = (initialLocations || []).map((location) => ({
...location,
deviceCount: location.deviceCount ?? location.cw_devices?.length ?? 0,
cw_devices: location.cw_devices ? [...location.cw_devices] : []
}));

// Preserve existing selection if present; otherwise default to "All Locations"
if (selectedLocationId === null) {
devices = locations.flatMap((location) => location.cw_devices || []);
} else {
const selected = locations.find((loc) => loc.location_id === selectedLocationId);
devices = selected?.cw_devices ? [...selected.cw_devices] : [];
}

loadingDevices = false;
deviceError = null;

return locations;
}

// Function to fetch all locations for a user
async function fetchLocations(userId: string) {
try {
Expand Down Expand Up @@ -120,7 +147,14 @@ async function loadDevicesForLocation(locationId: number) {
loadingDevices = true;
devices = [];

// Fetch devices for the selected location
const existing = locations.find((location) => location.location_id === locationId);
if (existing && existing.cw_devices) {
devices = [...existing.cw_devices];
loadingDevices = false;
return devices;
}

// Fetch devices for the selected location if not already loaded
const response = await fetch(`/api/locations/${locationId}/devices`);
if (!response.ok) throw new Error('Failed to fetch devices');

Expand Down Expand Up @@ -244,6 +278,7 @@ export function getLocationsStore() {
},

// Methods
initialize,
fetchLocations,
selectLocation,
loadDevicesForLocation,
Expand Down
Loading