Skip to content

Commit 644d9ce

Browse files
committed
refactor: Group releases under same application
This PR replaces the table based layout with a cleaner, grouped view of deployed applications for better readability. Applications are now grouped and displayed as expandable items Each application shows latest release version, status badge, and quick navigation to deployment details page. Expanded view lists all releases with corresponding statuses and navigation actions Additionally, CollapseItem has been refactored to be fully prop driven: - Removed old layout structure - Introduced className based props for header/content customization - Improved reusability and flexibility for future UI changes Signed-off-by: Jasmina <jasmina.piric@secomind.com>
1 parent 1c47da0 commit 644d9ce

13 files changed

Lines changed: 355 additions & 348 deletions

frontend/src/components/CollapseItem.tsx

Lines changed: 82 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
* This file is part of Edgehog.
33
*
4-
* Copyright 2025 SECO Mind Srl
4+
* Copyright 2025 - 2026 SECO Mind Srl
55
*
66
* Licensed under the Apache License, Version 2.0 (the "License");
77
* you may not use this file except in compliance with the License.
@@ -18,14 +18,11 @@
1818
* SPDX-License-Identifier: Apache-2.0
1919
*/
2020

21-
import { useState } from "react";
21+
import { useCallback, useRef, useState } from "react";
2222
import { useIntl } from "react-intl";
23-
import { Card, Button, Collapse } from "react-bootstrap";
23+
import { Button, Collapse } from "react-bootstrap";
2424

2525
import Icon from "@/components/Icon";
26-
import ContainerStatus, {
27-
parseContainerState,
28-
} from "@/components/ContainerStatus";
2926

3027
export function useCollapseToggle(defaultOpen = false) {
3128
const [open, setOpen] = useState(defaultOpen);
@@ -50,32 +47,26 @@ export function useCollapsibleSections<T extends string | number>(
5047
return { openSections, toggleSection, isSectionOpen, setOpenSections };
5148
}
5249

53-
interface CollapseCaretProps {
54-
open: boolean;
55-
}
56-
57-
const CollapseCaret = ({ open }: CollapseCaretProps) => {
58-
return (
59-
<span
60-
style={{
61-
display: "inline-flex",
62-
transition: "transform 0.2s ease-in-out",
63-
transform: open ? "rotate(0deg)" : "rotate(-180deg)",
64-
}}
65-
>
66-
<Icon icon="caretDown" />
67-
</span>
68-
);
69-
};
50+
const CollapseCaret = ({ open }: { open: boolean }) => (
51+
<span
52+
style={{
53+
display: "inline-flex",
54+
transition: "transform 0.2s ease-in-out",
55+
transform: open ? "rotate(0deg)" : "rotate(-180deg)",
56+
}}
57+
>
58+
<Icon icon="caretDown" />
59+
</span>
60+
);
7061

71-
interface CollapseHeaderButtonProps {
62+
type CollapseHeaderButtonProps = {
7263
open: boolean;
7364
onToggle: () => void;
7465
children: React.ReactNode;
7566
className?: string;
7667
style?: React.CSSProperties;
7768
title?: string;
78-
}
69+
};
7970

8071
const CollapseHeaderButton = ({
8172
open,
@@ -96,98 +87,89 @@ const CollapseHeaderButton = ({
9687
{children}
9788
</Button>
9889
);
99-
100-
type CollapseType = "flat" | "card-parent" | "card-child";
101-
102-
interface CollapseItemProps {
90+
type CollapseItemProps = {
10391
title: React.ReactNode;
10492
children: React.ReactNode;
93+
rightContent?: React.ReactNode;
94+
caretPosition?: "left" | "right" | "end";
10595
open: boolean;
10696
onToggle: () => void;
107-
containerStatus?: string | null;
108-
isInsideTable?: boolean;
109-
type?: CollapseType;
110-
}
97+
showToggleTooltip?: boolean;
98+
style?: React.CSSProperties;
99+
className?: string;
100+
headerClassName?: string;
101+
contentClassName?: string;
102+
};
111103

112104
const CollapseItem = ({
113105
title,
114106
children,
107+
rightContent,
108+
caretPosition,
115109
open,
116110
onToggle,
117-
containerStatus,
118-
isInsideTable = false,
119-
type = "card-child",
111+
showToggleTooltip = false,
112+
style,
113+
className,
114+
headerClassName,
115+
contentClassName,
120116
}: CollapseItemProps) => {
121117
const intl = useIntl();
122-
123-
const isFlat = type === "flat";
124-
const isParent = type === "card-parent";
125-
126-
if (isFlat) {
127-
const collapseTitle = isInsideTable
128-
? open
129-
? intl.formatMessage({
130-
id: "components.CollapseItem.collapseList",
131-
defaultMessage: "Collapse list",
132-
})
133-
: intl.formatMessage({
134-
id: "components.CollapseItem.expandList",
135-
defaultMessage: "Expand list",
136-
})
137-
: undefined;
138-
139-
return (
140-
<div
141-
className={
142-
!isInsideTable ? `mb-2 border-bottom ${open ? "pb-4" : "pb-1"}` : ""
143-
}
144-
>
145-
<CollapseHeaderButton
146-
open={open}
147-
onToggle={onToggle}
148-
title={collapseTitle}
149-
className={`d-flex align-items-center ps-0 pe-1 ${!isInsideTable ? "fw-bold" : ""}`}
150-
style={{ backgroundColor: "transparent", border: "none" }}
151-
>
152-
<span className="d-flex align-items-center gap-2">
153-
{title}
154-
<CollapseCaret open={open} />
155-
</span>
156-
</CollapseHeaderButton>
157-
158-
<Collapse in={open}>
159-
<div className={isInsideTable ? "" : "pt-3"}>{children}</div>
160-
</Collapse>
161-
</div>
162-
);
163-
}
118+
const containerRef = useRef<HTMLDivElement | null>(null);
119+
120+
const handleScrollIntoView = useCallback(() => {
121+
if (!containerRef.current) return;
122+
123+
const rect = containerRef.current.getBoundingClientRect();
124+
if (rect.bottom > window.innerHeight) {
125+
containerRef.current.scrollIntoView({
126+
behavior: "smooth",
127+
block: "nearest",
128+
});
129+
}
130+
}, []);
131+
132+
const collapseTitle = showToggleTooltip
133+
? open
134+
? intl.formatMessage({
135+
id: "components.CollapseItem.collapseList",
136+
defaultMessage: "Collapse list",
137+
})
138+
: intl.formatMessage({
139+
id: "components.CollapseItem.expandList",
140+
defaultMessage: "Expand list",
141+
})
142+
: undefined;
143+
144+
const renderCaret = () => <CollapseCaret open={open} />;
164145

165146
return (
166-
<Card className={`shadow-sm ${isParent ? "mb-3" : "mb-2"}`}>
167-
<Card.Header className="p-0">
168-
<CollapseHeaderButton
169-
open={open}
170-
onToggle={onToggle}
171-
className={`w-100 d-flex align-items-center ${isParent ? "fw-bold p-2" : "fw-semibold p-1"}`}
172-
style={{ fontSize: isParent ? "1rem" : "0.9rem" }}
173-
>
147+
<div ref={containerRef} className={className}>
148+
<CollapseHeaderButton
149+
open={open}
150+
onToggle={onToggle}
151+
title={collapseTitle}
152+
className={`d-flex align-items-center w-100 ${headerClassName ?? ""}`}
153+
style={style}
154+
>
155+
<div className="d-flex align-items-center gap-2">
156+
{caretPosition === "left" && renderCaret()}
174157
<span>{title}</span>
175-
176-
<span className="ms-auto d-inline-flex gap-2 align-items-center">
177-
{containerStatus && (
178-
<ContainerStatus state={parseContainerState(containerStatus)} />
179-
)}
180-
<CollapseCaret open={open} />
181-
</span>
182-
</CollapseHeaderButton>
183-
</Card.Header>
184-
185-
<Collapse in={open}>
186-
<div className={`border-top ${isParent ? "p-3" : "p-2"}`}>
187-
{children}
158+
{caretPosition === "right" && renderCaret()}
188159
</div>
160+
161+
{(rightContent || caretPosition === "end") && (
162+
<div className="ms-auto d-flex align-items-center gap-2">
163+
{caretPosition === "end" && renderCaret()}
164+
{rightContent}
165+
</div>
166+
)}
167+
</CollapseHeaderButton>
168+
169+
<Collapse in={open} onEntered={handleScrollIntoView}>
170+
<div className={contentClassName}>{children}</div>
189171
</Collapse>
190-
</Card>
172+
</div>
191173
);
192174
};
193175

0 commit comments

Comments
 (0)