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
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ const Container = styled(Box)`
justify-content: space-between;
`

export default function DashboardElection() {
interface DashboardElectionProps {
refreshRef?: React.RefObject<HTMLButtonElement | null>
}

export default function DashboardElection({refreshRef}: DashboardElectionProps) {
const {i18n} = useTranslation()
const [tenantId] = useTenantStore()
const {globalSettings} = useContext(SettingsContext)
Expand All @@ -49,7 +53,11 @@ export default function DashboardElection() {
// Ensure required parameters are set before running the query.
const canQueryStats = Boolean(tenantId && record?.election_event_id && record?.id)

const {loading, data: dataStats} = useQuery<GetElectionStatsQuery>(GET_ELECTION_STATS, {
const {
loading,
data: dataStats,
refetch: doRefetch,
} = useQuery<GetElectionStatsQuery>(GET_ELECTION_STATS, {
variables: {
tenantId,
electionEventId: record?.election_event_id,
Expand Down Expand Up @@ -83,6 +91,13 @@ export default function DashboardElection() {
return (
<Box sx={{width: 1024, marginX: "auto"}} className="dashboard">
<Box>
<button
ref={refreshRef}
onClick={() => {
doRefetch()
Comment on lines +96 to +97
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hidden refresh <button> should set type="button" to avoid inadvertently submitting a surrounding form, and the handler should guard against missing query variables (e.g., canQueryStats false) before calling doRefetch(). Also consider handling the returned promise (e.g., void doRefetch() / catch) to avoid unhandled rejections if the refetch errors.

Suggested change
onClick={() => {
doRefetch()
type="button"
onClick={() => {
if (!canQueryStats) {
return
}
void doRefetch()

Copilot uses AI. Check for mistakes.
}}
style={{display: "none"}}
/>
<Stats metrics={metrics} />

<Container>
Expand Down
14 changes: 10 additions & 4 deletions packages/admin-portal/src/resources/Election/ElectionTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: AGPL-3.0-only

import React, {Suspense, useContext, useEffect, useMemo, useState} from "react"
import React, {Suspense, useContext, useEffect, useMemo, useRef, useState} from "react"
import {useTranslation} from "react-i18next"
import {useRecordContext, useSidebarState, RecordContextProvider} from "react-admin"
import {v4 as uuidv4} from "uuid"
Expand All @@ -27,9 +27,11 @@ import {useAliasRenderer} from "@/hooks/useAliasRenderer"
// Stable Tab Components
// ---------------------------------------------------------------------

const DashboardTab: React.FC = () => (
const DashboardTab: React.FC<{refreshRef: React.RefObject<HTMLButtonElement | null>}> = ({
refreshRef,
}) => (
<Suspense fallback={<div>Loading Dashboard...</div>}>
<DashboardElection />
<DashboardElection refreshRef={refreshRef} />
</Suspense>
)

Expand Down Expand Up @@ -85,6 +87,7 @@ export const ElectionTabs: React.FC = () => {
const [hasPermissionToViewElection, setHasPermissionToViewElection] = useState<boolean>(true)
const [open] = useSidebarState()
const aliasRenderer = useAliasRenderer()
const refreshRef = useRef<HTMLButtonElement | null>(null)

const isElectionEventLocked =
record?.presentation?.locked_down === EElectionEventLockedDown.LOCKED_DOWN
Expand Down Expand Up @@ -135,14 +138,17 @@ export const ElectionTabs: React.FC = () => {
const tabs = useMemo(() => {
const result: Array<{
label: string
component: React.FC
component: React.FC<any>
props?: any
action?: (index: number) => void
}> = []

if (showDashboard) {
result.push({
label: t("electionScreen.tabs.dashboard"),
component: DashboardTab,
props: {refreshRef},
action: () => refreshRef.current?.click(),
})
Comment on lines 146 to 152
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The props: {refreshRef} entry in the tab definition is currently ignored by the shared Tabs component (it only renders <SelectedComponent {...propsFromTabs} />). As a result, DashboardTab never receives refreshRef, so the hidden refresh button ref is never attached and action: () => refreshRef.current?.click() will always be a no-op. Fix by either (a) passing refreshRef as a prop to <Tabs ...> (so it gets forwarded to the selected component), or (b) enhancing Tabs to merge elements[selectedTab].props into the selected component props.

Copilot uses AI. Check for mistakes.
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ export const ElectionEventTabs: React.FC = () => {
label: t("electionEventScreen.tabs.dashboard"),
component: DashboardTab,
props: {refreshRef, handleChildMount},
action: () => refreshRef.current?.click(),
Comment on lines 323 to +326
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This tab action relies on refreshRef.current?.click(), but refreshRef is only placed on the tab object (props: {refreshRef, handleChildMount}) and the shared Tabs component does not pass per-tab props to the selected component. That means the dashboard’s hidden refresh button never receives the ref and this action will do nothing. Fix by passing refreshRef/handleChildMount to <Tabs> (common props) or by updating Tabs to apply elements[selectedTab].props when rendering the selected tab.

Copilot uses AI. Check for mistakes.
})
}

Expand Down
Loading