Skip to content

Commit 1978908

Browse files
authored
Merge pull request #86 from moda20/Improvement/dashboard-ui-element-improvements
2 parents 49249e1 + 66f38af commit 1978908

15 files changed

Lines changed: 533 additions & 260 deletions

src/components/custom/DrawerJobEvents.tsx

Lines changed: 69 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
11
import SheetActionDialog from "@/components/sheet-action-dialog"
22
import type { jobsTableData } from "@/features/jobsTable/interfaces"
3-
import type { ReactNode } from "react"
3+
import { ReactNode, useEffect } from "react"
44
import { useCallback } from "react"
5+
import { useMemo, useRef } from "react"
56
import { useState } from "react"
67
import { JobEventTypes } from "@/models/jobs"
78
import { useJobEvents } from "@/hooks/useJobEvents"
89
import { EventItem } from "@/components/custom/general/EventItem"
9-
import { ScrollArea } from "@/components/ui/scroll-area"
1010
import { Separator } from "@/components/ui/separator"
1111
import { ManagedSimpleDropdown } from "@/components/custom/ManagedSimpleDropdown"
1212
import { Spinner } from "@/components/ui/spinner"
1313
import { ButtonWithTooltip } from "@/components/custom/general/ButtonWithTooltip"
1414
import { Switch } from "@/components/ui/switch"
1515
import { cn } from "@/lib/utils"
1616
import { ListCheck } from "lucide-react"
17+
import { useVirtualizer } from "@tanstack/react-virtual"
18+
import LoadingOverlay from "@/components/custom/LoadingOverlay"
19+
import { debounce } from "@/utils/generalUtils"
1720

1821
export interface DrawerNewComponentProps {
1922
JobDetails: jobsTableData
@@ -36,6 +39,7 @@ export default function DrawerJobEvents({
3639
JobEventTypes.INFO,
3740
])
3841
const [unreadOnly, setUnreadOnly] = useState(false)
42+
const parentRef = useRef<HTMLDivElement>(null)
3943

4044
const { events, latestEvents, loading, setEventsToHandled } = useJobEvents({
4145
jobId: JobDetails.id,
@@ -63,6 +67,29 @@ export default function DrawerJobEvents({
6367
[filteredEventTypes],
6468
)
6569

70+
const allEvents = useMemo(() => {
71+
return [...latestEvents, ...events]
72+
}, [events, latestEvents, latestEvents.length, events.length])
73+
74+
const rowVirtualizer = useVirtualizer({
75+
count: allEvents.length,
76+
getScrollElement: () => parentRef.current,
77+
estimateSize: () => 80,
78+
overscan: 40,
79+
gap: 8,
80+
lanes: 1,
81+
enabled: allEvents.length > 0,
82+
initialOffset: 0,
83+
})
84+
85+
const refreshVirtualizer = useCallback(() => {
86+
setTimeout(() => {
87+
// TODO find another solution for this timeout issue :
88+
// the allEvent array although is always updated, doesn't trigger the virtualizer getVirtualItems() function to return filled item array
89+
rowVirtualizer.measure()
90+
}, 200)
91+
}, [rowVirtualizer])
92+
6693
return (
6794
<SheetActionDialog
6895
side={"right"}
@@ -71,6 +98,8 @@ export default function DrawerJobEvents({
7198
trigger={trigger}
7299
modal={true}
73100
contentClassName="w-[600px] sm:w-[800px] sm:max-w-[800px]"
101+
onOpenChange={refreshVirtualizer}
102+
innerContainerClassName={"gap-2"}
74103
>
75104
<div className="mt-2 flex gap-2 w-full">
76105
<ManagedSimpleDropdown
@@ -106,36 +135,45 @@ export default function DrawerJobEvents({
106135
</ButtonWithTooltip>
107136
</div>
108137
</div>
109-
<ScrollArea>
110-
<div className={"flex flex-col gap-2 py-4"}>
111-
{latestEvents.length > 0 &&
112-
latestEvents.map(ev => (
113-
<EventItem
114-
key={ev.id}
115-
timestamp={ev.created_at}
116-
message={ev.event_message}
117-
type={ev.type}
118-
onHandle={setEventsToRead(ev.id)}
119-
handled={ev.handled}
120-
handleTime={ev.handled_on}
121-
job={JobDetails}
122-
/>
123-
))}
124-
{latestEvents.length > 0 && <Separator />}
125-
{events.map(ev => (
126-
<EventItem
127-
key={ev.id}
128-
timestamp={ev.created_at}
129-
message={ev.event_message}
130-
type={ev.type}
131-
onHandle={setEventsToRead(ev.id)}
132-
handled={ev.handled}
133-
handleTime={ev.handled_on}
134-
job={JobDetails}
135-
/>
136-
))}
138+
<LoadingOverlay isLoading={loading}>
139+
<div
140+
ref={parentRef}
141+
className="overflow-auto h-[calc(100vh-10rem)] w-full"
142+
>
143+
<div
144+
className="relative w-full py-4"
145+
style={{
146+
height: `${rowVirtualizer.getTotalSize()}px`,
147+
}}
148+
key={allEvents.length}
149+
>
150+
{rowVirtualizer.getVirtualItems().map(virtualRow => {
151+
const ev = allEvents[virtualRow.index]
152+
return (
153+
<div
154+
className="absolute w-full"
155+
style={{
156+
transform: `translateY(${virtualRow.start}px)`,
157+
}}
158+
key={virtualRow.key}
159+
data-index={virtualRow.index}
160+
ref={rowVirtualizer.measureElement}
161+
>
162+
<EventItem
163+
timestamp={ev.created_at}
164+
message={ev.event_message}
165+
type={ev.type}
166+
onHandle={setEventsToRead(ev.id)}
167+
handled={ev.handled}
168+
handleTime={ev.handled_on}
169+
job={JobDetails}
170+
/>
171+
</div>
172+
)
173+
})}
174+
</div>
137175
</div>
138-
</ScrollArea>
176+
</LoadingOverlay>
139177
</SheetActionDialog>
140178
)
141179
}

src/components/custom/DrawerLatestRuns.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export default function DrawerLatestRuns({
4141
const [selectedLogItem, setSelectedLogItem] = useState<any | undefined>(
4242
undefined,
4343
)
44-
const [itemsTotal, setItemstotal] = useState(0)
44+
const [itemsTotal, setItemsTotal] = useState(0)
4545
const [inputSchema, setInputSchema] = useState<JobRunsQuerySchema>({
4646
jobId: JobDetails.id,
4747
limit: 10,
@@ -67,7 +67,7 @@ export default function DrawerLatestRuns({
6767

6868
const resetDrawer = useCallback(() => {
6969
setLogItems([])
70-
setItemstotal(0)
70+
setItemsTotal(0)
7171
setInputSchema({
7272
...inputSchema,
7373
offset: 0,
@@ -132,15 +132,15 @@ export default function DrawerLatestRuns({
132132
title={`Job runs for ${JobDetails.name}`}
133133
description={"List of previous job run logs"}
134134
contentClassName={cn(
135-
"transition-all duration-200",
135+
"transition-all duration-100",
136136
showLogs ? "w-[600px] sm:w-[900px] sm:max-w-[80vw]" : "",
137137
)}
138138
trigger={trigger}
139139
onOpenChange={v => {
140140
if (v) {
141141
getLatestRuns().then(data => {
142142
setLogItems(data.data)
143-
setItemstotal(data.total)
143+
setItemsTotal(data.total)
144144
})
145145
} else {
146146
resetDrawer()
@@ -164,15 +164,15 @@ export default function DrawerLatestRuns({
164164
itemClassName={(item: any) => {
165165
return cn(
166166
"focus:rounded-lg outline-none focus:ring-2 focus:ring-opacity-50 focus-visible:ring-2 focus-visible:ring-offset-0 focus-visible:ring-opacity-50 hover:ring-2 hover:ring-opacity-50 hover:rounded-lg focus:ring-blue-500 focus-visible:ring-blue-500 hover:ring-blue-500",
167-
item.error ? "ring-destructive ring-2 rounded-xl" : "",
167+
item.error ? "border-destructive border-2 rounded-xl" : "",
168168
)
169169
}}
170170
renderItem={(logParent: any) => {
171171
const CardIcon = () =>
172172
logParent.isUnfinished ? (
173173
<LoaderIcon className="text-foreground animate-spin duration-2000" />
174174
) : logParent.error ? (
175-
<FileWarning className="h-8 w-8 text-foreground bg-destructive p-1 rounded-lg" />
175+
<FileWarning className="h-7 w-7 text-foreground bg-destructive p-1 rounded-lg" />
176176
) : (
177177
<CheckCircle className="h-8 w-8 text-success p-1 rounded-lg border-2 border-border" />
178178
)

src/components/custom/dashboard/statsBar.tsx

Lines changed: 133 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
2-
import { FileWarningIcon, FolderKanban } from "lucide-react"
1+
import { Card, CardContent } from "@/components/ui/card"
2+
import { FileWarningIcon, Calendar, Play, Database } from "lucide-react"
33
import type { ReactNode } from "react"
44
import { useEffect, useState } from "react"
55
import jobsService from "@/services/JobsService"
@@ -15,6 +15,84 @@ export interface StatsBarProps {
1515
metrics?: any
1616
}
1717

18+
interface MetricSectionProps {
19+
title: string
20+
value: ReactNode
21+
icon: ReactNode
22+
textSize: string
23+
bgColor: string
24+
bgColorLight: string
25+
hasError?: boolean
26+
tooltipContent?: string
27+
}
28+
29+
function MetricSection({
30+
title,
31+
value,
32+
icon,
33+
textSize,
34+
bgColor,
35+
bgColorLight,
36+
hasError,
37+
tooltipContent,
38+
}: MetricSectionProps) {
39+
return (
40+
<div
41+
className={cn(
42+
"relative p-5 transition-all duration-300",
43+
hasError ? "bg-destructive/5" : `${bgColorLight}/5`,
44+
)}
45+
>
46+
{!hasError && (
47+
<div className={cn("absolute top-0 right-0 w-1 h-full", bgColor)} />
48+
)}
49+
<div className="flex items-start justify-between">
50+
<div className="flex-1">
51+
<p className="text-sm font-medium uppercase tracking-wider text-muted-foreground mb-1">
52+
{title}
53+
</p>
54+
<div className="flex items-baseline gap-2">
55+
<WithError errorCpt={`--`} hasError={hasError}>
56+
<span
57+
className={cn(
58+
"font-bold",
59+
textSize === "text-4xl"
60+
? "text-4xl"
61+
: textSize === "text-3xl"
62+
? "text-3xl"
63+
: "text-2xl",
64+
hasError ? "text-destructive" : "text-foreground",
65+
)}
66+
>
67+
{value}
68+
</span>
69+
</WithError>
70+
</div>
71+
</div>
72+
<div
73+
className={cn(
74+
"flex-shrink-0 ml-4",
75+
hasError ? "text-destructive" : bgColor,
76+
)}
77+
>
78+
{hasError ? (
79+
<Tooltip>
80+
<TooltipTrigger>
81+
<FileWarningIcon size={28} />
82+
</TooltipTrigger>
83+
<TooltipContent className="text-destructive border-destructive">
84+
{tooltipContent || "Error fetching data"}
85+
</TooltipContent>
86+
</Tooltip>
87+
) : (
88+
icon
89+
)}
90+
</div>
91+
</div>
92+
</div>
93+
)
94+
}
95+
1896
export default function StatsBar(props: StatsBarProps) {
1997
const [jobMetrics, setJobMetrics] = useState(props.metrics ?? {})
2098
const [hasError, setHasError] = useState(false)
@@ -35,48 +113,59 @@ export default function StatsBar(props: StatsBarProps) {
35113
}, [])
36114

37115
return (
38-
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
39-
<Card className={cn("border-border", hasError && "border-destructive")}>
40-
<CardHeader className="flex flex-row items-center justify-between space-y-0 p-4 pb-2 text-foreground bg-background border-border rounded-t-xl">
41-
<CardTitle className="text-sm font-medium">
42-
Number of Registered jobs
43-
</CardTitle>
44-
{hasError ? (
45-
<Tooltip>
46-
<TooltipTrigger>
47-
<FileWarningIcon className="text-destructive" />
48-
</TooltipTrigger>
49-
<TooltipContent className="text-destructive border-destructive">
50-
Error fetching job metrics
51-
</TooltipContent>
52-
</Tooltip>
53-
) : (
54-
<FolderKanban />
55-
)}
56-
</CardHeader>
57-
<CardContent className="p-4 pt-0">
58-
<div className="text-2xl font-bold mb-2">
59-
<WithError errorCpt={`...`} hasError={hasError}>
60-
{jobMetrics.job_count}
61-
</WithError>
62-
</div>
63-
<div className="flex flex-col gap-1">
64-
<p className="text-xs text-muted-foreground flex items-center gap-1">
65-
<span className="text-md font-bold">
66-
<WithError errorCpt={`N/A`} hasError={hasError}>
67-
{jobMetrics.running_jobs_count}
68-
</WithError>
69-
</span>
70-
are currently scheduled
71-
</p>
72-
<p className="text-xs text-muted-foreground flex items-center gap-1">
73-
<span className="text-md font-bold">
74-
<WithError errorCpt={`N/A`} hasError={hasError}>
75-
{jobMetrics.runningJobsCount}
76-
</WithError>
77-
</span>
78-
are currently running
79-
</p>
116+
<div className="w-full xl:w-1/2">
117+
<Card
118+
className={cn(
119+
"border-border overflow-hidden",
120+
hasError && "border-destructive",
121+
)}
122+
>
123+
<CardContent className="p-0">
124+
<div className="flex flex-col md:flex-row">
125+
<div className="flex-1">
126+
<MetricSection
127+
title="Registered Jobs"
128+
value={jobMetrics.job_count}
129+
icon={<Database size={28} />}
130+
textSize="text-4xl"
131+
bgColor="text-metric1"
132+
bgColorLight="bg-metric1-light"
133+
hasError={hasError}
134+
tooltipContent="Error fetching registered jobs"
135+
/>
136+
</div>
137+
138+
<div className="hidden md:block w-px bg-border my-5" />
139+
<div className="md:hidden h-px bg-border mx-5" />
140+
141+
<div className="flex-1">
142+
<MetricSection
143+
title="Scheduled Jobs"
144+
value={jobMetrics.running_jobs_count}
145+
icon={<Calendar size={28} />}
146+
textSize="text-3xl"
147+
bgColor="text-metric2"
148+
bgColorLight="bg-metric2-light"
149+
hasError={hasError}
150+
tooltipContent="Error fetching scheduled jobs"
151+
/>
152+
</div>
153+
154+
<div className="hidden md:block w-px bg-border my-5" />
155+
<div className="md:hidden h-px bg-border mx-5" />
156+
157+
<div className="flex-1">
158+
<MetricSection
159+
title="Running Jobs"
160+
value={jobMetrics.runningJobsCount}
161+
icon={<Play size={28} className="fill-current" />}
162+
textSize="text-2xl"
163+
bgColor="text-metric3"
164+
bgColorLight="bg-metric3-light"
165+
hasError={hasError}
166+
tooltipContent="Error fetching running jobs"
167+
/>
168+
</div>
80169
</div>
81170
</CardContent>
82171
</Card>

0 commit comments

Comments
 (0)