From b2ea83393b6b2549005f3461e9e014d37d7e6121 Mon Sep 17 00:00:00 2001 From: Mohammed Faisal Hussain Date: Tue, 31 Oct 2023 15:09:37 +0530 Subject: [PATCH 1/4] feat: enhanced sidepanel and added it to storybook --- .../sidepanel/Sidepanel.stories.tsx | 114 ++++++++++++++++++ dashboard/components/sidepanel/Sidepanel.tsx | 2 +- .../components/sidepanel/SidepanelFooter.tsx | 37 ++++++ .../components/sidepanel/SidepanelHeader.tsx | 77 ++++++++++-- .../components/sidepanel/SidepanelPage.tsx | 6 +- .../components/sidepanel/SidepanelTabs.tsx | 5 +- 6 files changed, 224 insertions(+), 17 deletions(-) create mode 100644 dashboard/components/sidepanel/Sidepanel.stories.tsx create mode 100644 dashboard/components/sidepanel/SidepanelFooter.tsx diff --git a/dashboard/components/sidepanel/Sidepanel.stories.tsx b/dashboard/components/sidepanel/Sidepanel.stories.tsx new file mode 100644 index 000000000..62b530a3f --- /dev/null +++ b/dashboard/components/sidepanel/Sidepanel.stories.tsx @@ -0,0 +1,114 @@ +import { useEffect, useState } from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; +import Button from '@components/button/Button'; +import Sidepanel from './Sidepanel'; +import SidepanelHeader from './SidepanelHeader'; +import SidepanelTabs from './SidepanelTabs'; +import SidepanelPage from './SidepanelPage'; +import SidepanelFooter from './SidepanelFooter'; + +type SidepanelProps = { + title: string; + subtitle?: string; + href?: string; + imgSrc?: string; + imgAlt?: string; + deleteLabel?: string; + saveLabel?: string; + tabs: string[]; +}; + +function SidepanelWrapper({ + title, + tabs, + saveLabel, + ...others +}: SidepanelProps) { + const [isOpen, setIsOpen] = useState(false); + const [page, setPage] = useState(''); + + useEffect(() => { + if (tabs && tabs.length > 0) { + setPage(tabs[0]); + } + }, []); + + const open = () => setIsOpen(true); + const close = () => setIsOpen(false); + const goTo = (newPage: string) => setPage(newPage); + + return ( +
+ + + + + {tabs && + tabs.length > 0 && + tabs?.map((tab: string) => ( + +

+ Lorem ipsum, dolor sit amet consectetur adipisicing elit. + Tempora et officia tenetur est, minima veritatis doloremque + accusantium distinctio animi nulla reprehenderit quod asperiores + similique illum perferendis, reiciendis, ipsam doloribus sit! + Quidem amet veritatis ipsa omnis inventore architecto, assumenda + ad vero cupiditate pariatur natus nisi corporis. Nobis voluptas + vitae similique cupiditate deleniti. Inventore eos iusto porro + perspiciatis fugiat nam sequi eligendi voluptas autem. Vitae + beatae animi porro, fugiat eligendi hic cumque illum! + Consectetur culpa obcaecati dolore praesentium harum. Provident + nesciunt repudiandae eligendi quos, minima sed dolore veniam + consequatur delectus! Optio ratione cum dolor eaque + necessitatibus numquam maiores inventore asperiores quisquam + quidem? +

+
+ ))} + +
+
+ ); +} + +const meta: Meta = { + title: 'Komiser/SidePanel', + component: SidepanelWrapper, + tags: ['autodocs'], + argTypes: {} +}; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + args: { + title: 'Resource name', + tabs: ['tab 1', 'tab 2', 'tab 3'], + subtitle: 'service', + href: 'https://docs.komiser.io/', + imgSrc: '/assets/img/providers/aws.png', + imgAlt: 'komiser', + deleteLabel: 'delete', + saveLabel: 'Save changes' + } +}; + +export const Secondary: Story = { + args: { + title: 'Create new policy', + tabs: ['tab 1', 'tab 2', 'tab 3'], + deleteLabel: 'delete', + saveLabel: 'Save changes' + } +}; diff --git a/dashboard/components/sidepanel/Sidepanel.tsx b/dashboard/components/sidepanel/Sidepanel.tsx index 9daf0cbdf..52bbbbe0d 100644 --- a/dashboard/components/sidepanel/Sidepanel.tsx +++ b/dashboard/components/sidepanel/Sidepanel.tsx @@ -32,7 +32,7 @@ function Sidepanel({ isOpen, closeModal, children, noScroll }: SidepanelProps) { className="fixed inset-0 z-30 hidden animate-fade-in bg-black-900/10 opacity-0 sm:block" >
diff --git a/dashboard/components/sidepanel/SidepanelFooter.tsx b/dashboard/components/sidepanel/SidepanelFooter.tsx new file mode 100644 index 000000000..1da7fee56 --- /dev/null +++ b/dashboard/components/sidepanel/SidepanelFooter.tsx @@ -0,0 +1,37 @@ +import Button from '@components/button/Button'; + +export type SidepanelFooterProps = { + loading?: boolean; + closeModal: () => void; + saveAction: () => void; + saveLabel?: string; +}; + +function SidepanelFooter({ + closeModal, + saveAction, + saveLabel, + loading +}: SidepanelFooterProps) { + return ( + <> +
+
+ + +
+
+ + ); +} + +export default SidepanelFooter; diff --git a/dashboard/components/sidepanel/SidepanelHeader.tsx b/dashboard/components/sidepanel/SidepanelHeader.tsx index 6d2bfd6bc..c4832ff06 100644 --- a/dashboard/components/sidepanel/SidepanelHeader.tsx +++ b/dashboard/components/sidepanel/SidepanelHeader.tsx @@ -1,32 +1,83 @@ +import { ReactNode } from 'react'; +import ArrowLeftIcon from '@components/icons/ArrowLeftIcon'; +import HyperLinkIcon from '@components/icons/HyperLinkIcon'; import Button from '../button/Button'; -type SidepanelHeaderProps = { +export type SidepanelHeaderProps = { title: string; - subtitle: string; + subtitle?: string; + href?: string; + imgSrc?: string; + imgAlt?: string; + children?: ReactNode; closeModal: () => void; deleteAction?: () => void; + goBack?: () => void; deleteLabel?: string; }; function SidepanelHeader({ title, subtitle, + href, + imgSrc, + imgAlt, + children, closeModal, deleteAction, - deleteLabel + deleteLabel, + goBack }: SidepanelHeaderProps) { return ( -
-
-
-

- {title} -

-

- {subtitle} -

+
+ {title && subtitle && ( +
+ {imgSrc && ( + + {imgAlt} + + )} +
+

+ {title} + + + +

+

+ {subtitle} +

+
-
+ )} + + {title && !subtitle && ( +
+ +
+

+ {title} +

+
+
+ )} + + {children}
{deleteAction && ( diff --git a/dashboard/components/sidepanel/SidepanelPage.tsx b/dashboard/components/sidepanel/SidepanelPage.tsx index c1571e72b..124fbe6e3 100644 --- a/dashboard/components/sidepanel/SidepanelPage.tsx +++ b/dashboard/components/sidepanel/SidepanelPage.tsx @@ -16,7 +16,11 @@ function SidepanelPage({ return ( <> {page === param && ( -
+
{children}
)} diff --git a/dashboard/components/sidepanel/SidepanelTabs.tsx b/dashboard/components/sidepanel/SidepanelTabs.tsx index b1075a66f..453789fb4 100644 --- a/dashboard/components/sidepanel/SidepanelTabs.tsx +++ b/dashboard/components/sidepanel/SidepanelTabs.tsx @@ -1,4 +1,4 @@ -type SidepanelTabsProps = { +export type SidepanelTabsProps = { goTo: (page: any) => void; page: string; tabs: string[]; @@ -19,7 +19,8 @@ function SidepanelTabs({ goTo, page, tabs }: SidepanelTabsProps) { : 'border-transparent hover:text-komiser-700' }`} > - {tab} + {tab.charAt(0).toUpperCase() + tab.slice(1).toLowerCase()}{' '} + {/* capitalize first letter */} ))} From db951685a0db4fa235606b7b99b5d16732a0118a Mon Sep 17 00:00:00 2001 From: Mohammed Faisal Hussain Date: Tue, 31 Oct 2023 15:11:11 +0530 Subject: [PATCH 2/4] feat: created pill component and its story --- dashboard/components/pill/Pill.mocks.tsx | 48 ++++++++++++++++++ dashboard/components/pill/Pill.stories.tsx | 57 +++++++++++++++++++++ dashboard/components/pill/Pill.tsx | 58 ++++++++++++++++++++++ 3 files changed, 163 insertions(+) create mode 100644 dashboard/components/pill/Pill.mocks.tsx create mode 100644 dashboard/components/pill/Pill.stories.tsx create mode 100644 dashboard/components/pill/Pill.tsx diff --git a/dashboard/components/pill/Pill.mocks.tsx b/dashboard/components/pill/Pill.mocks.tsx new file mode 100644 index 000000000..313f80ef3 --- /dev/null +++ b/dashboard/components/pill/Pill.mocks.tsx @@ -0,0 +1,48 @@ +import { PillProps } from './Pill'; + +const active: PillProps = { + status: 'active', + children: 'active' +}; + +const pending: PillProps = { + status: 'pending', + children: 'pending' +}; + +const removed: PillProps = { + status: 'removed', + children: 'removed' +}; + +const inactive: PillProps = { + status: 'inactive', + children: 'inactive' +}; + +const info: PillProps = { + status: 'info', + children: 'info' +}; + +const latest: PillProps = { + status: 'new', + children: 'latest' +}; + +const highlight: PillProps = { + status: 'highlight', + children: 'highlight' +}; + +const mockPillProps = { + active, + pending, + removed, + inactive, + info, + latest, + highlight +}; + +export default mockPillProps; diff --git a/dashboard/components/pill/Pill.stories.tsx b/dashboard/components/pill/Pill.stories.tsx new file mode 100644 index 000000000..98c69ecd8 --- /dev/null +++ b/dashboard/components/pill/Pill.stories.tsx @@ -0,0 +1,57 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import Pill from './Pill'; +import mockPillProps from './Pill.mocks'; + +// More on how to set up stories at: https://storybook.js.org/docs/7.0/react/writing-stories/introduction +const meta: Meta = { + title: 'Komiser/Pill', + component: Pill, + tags: ['autodocs'], + argTypes: {} +}; + +export default meta; +type Story = StoryObj; + +// More on writing stories with args: https://storybook.js.org/docs/7.0/react/writing-stories/args +export const Active: Story = { + args: { + ...mockPillProps.active + } +}; + +export const Pending: Story = { + args: { + ...mockPillProps.pending + } +}; + +export const Removed: Story = { + args: { + ...mockPillProps.removed + } +}; + +export const Inactive: Story = { + args: { + ...mockPillProps.inactive + } +}; + +export const Info: Story = { + args: { + ...mockPillProps.info + } +}; + +export const Latest: Story = { + args: { + ...mockPillProps.latest + } +}; + +export const Highlight: Story = { + args: { + ...mockPillProps.highlight + } +}; diff --git a/dashboard/components/pill/Pill.tsx b/dashboard/components/pill/Pill.tsx new file mode 100644 index 000000000..90ee465f5 --- /dev/null +++ b/dashboard/components/pill/Pill.tsx @@ -0,0 +1,58 @@ +import { ReactNode } from 'react'; + +export type PillProps = { + status: + | 'active' + | 'pending' + | 'removed' + | 'inactive' + | 'info' + | 'new' + | 'highlight'; + children: ReactNode; // Remove the quotes + textcase?: 'uppercase' | 'lowercase'; +}; + +function Pill({ status, children, textcase = 'lowercase' }: PillProps) { + function handleBgColor() { + let color; + + if (status === 'active') color = 'bg-green-100'; + if (status === 'pending') color = 'bg-orange-100'; + if (status === 'removed') color = 'bg-rose-100'; + if (status === 'inactive') color = 'bg-zinc-100'; + if (status === 'info') color = 'bg-blue-100'; + if (status === 'new') color = 'bg-sky-100'; + if (status === 'highlight') color = 'bg-violet-100'; + + return color; + } + + function handleFontColor() { + let color; + + if (status === 'active') color = 'text-green-400'; + if (status === 'pending') color = 'text-orange-400'; + if (status === 'removed') color = 'text-red-400'; + if (status === 'inactive') color = 'text-zinc-400'; + if (status === 'info') color = 'text-blue-500'; + if (status === 'new') color = 'text-teal-600'; + if (status === 'highlight') color = 'text-violet-600'; + + return color; + } + + return ( +
+

+ {children} +

+
+ ); +} + +export default Pill; From 204ff5ab56aeb81f397b5fa81e20f891c1fc92f8 Mon Sep 17 00:00:00 2001 From: Mohammed Faisal Hussain Date: Tue, 31 Oct 2023 15:13:24 +0530 Subject: [PATCH 3/4] feat: display node details in side panel #1072 --- .../components/explorer/DependencyGraph.tsx | 59 +++++- dashboard/components/icons/HyperLinkIcon.tsx | 22 +++ .../components/InventorySidePanel.tsx | 178 +++++++++++------- .../useInventory/types/useInventoryTypes.ts | 3 +- .../hooks/useInventory/useInventory.tsx | 4 +- dashboard/pages/inventory.tsx | 1 + dashboard/services/settingsService.ts | 13 ++ 7 files changed, 210 insertions(+), 70 deletions(-) create mode 100644 dashboard/components/icons/HyperLinkIcon.tsx diff --git a/dashboard/components/explorer/DependencyGraph.tsx b/dashboard/components/explorer/DependencyGraph.tsx index 6b115e14e..336113065 100644 --- a/dashboard/components/explorer/DependencyGraph.tsx +++ b/dashboard/components/explorer/DependencyGraph.tsx @@ -16,6 +16,9 @@ import EmptyState from '@components/empty-state/EmptyState'; import Tooltip from '@components/tooltip/Tooltip'; import WarningIcon from '@components/icons/WarningIcon'; +import useInventory from '@components/inventory/hooks/useInventory/useInventory'; +import settingsService from '@services/settingsService'; +import InventorySidePanel from '@components/inventory/components/InventorySidePanel'; import { ReactFlowData } from './hooks/useDependencyGraph'; import { edgeAnimationConfig, @@ -37,9 +40,38 @@ nodeHtmlLabel(Cytoscape.use(COSEBilkent)); Cytoscape.use(popper); const DependencyGraph = ({ data }: DependencyGraphProps) => { const [initDone, setInitDone] = useState(false); - const dataIsEmpty: boolean = data.nodes.length === 0; + const { + openModal, + isOpen, + closeModal, + data: inventoryItem, + page, + goTo, + tags, + handleChange, + addNewTag, + removeTag, + updateTags, + loading, + deleteLoading, + bulkItems, + updateBulkTags + } = useInventory(); + + // opens modal to display details of clicked node + const handleNodeClick = async (event: EventObject) => { + const nodeData = event.target.data(); + settingsService.getResourceById(`?resourceId=${nodeData.id}`).then(res => { + if (res === Error) { + console.log('Error retrieving resource by id'); + } else { + openModal(res); + } + }); + }; + // Type technically is Cytoscape.EdgeCollection but that throws an unexpected error const loopAnimation = (eles: any) => { const ani = eles.animation(edgeAnimationConfig[0], edgeAnimationConfig[1]); @@ -81,6 +113,8 @@ const DependencyGraph = ({ data }: DependencyGraphProps) => { cy.nodes().roots().addClass('root'); // Animate edges cy.edges().forEach(loopAnimation); + // Add a click event listener to the Cytoscape graph + cy.on('tap', 'node', handleNodeClick); // Add hover tooltip on edges cy.edges().bind('mouseover', event => { @@ -141,12 +175,13 @@ const DependencyGraph = ({ data }: DependencyGraphProps) => { return (
- {/* {console.log(node)})} maxZoom={maxZoom} minZoom={minZoom} layout={graphLayoutConfig} @@ -165,7 +200,7 @@ const DependencyGraph = ({ data }: DependencyGraphProps) => { } ]} cy={(cy: Cytoscape.Core) => cyActionHandlers(cy)} - /> */} + /> {dataIsEmpty ? ( <>
@@ -216,6 +251,24 @@ const DependencyGraph = ({ data }: DependencyGraphProps) => {
)}
+ {/* Modal */} +
); }; diff --git a/dashboard/components/icons/HyperLinkIcon.tsx b/dashboard/components/icons/HyperLinkIcon.tsx new file mode 100644 index 000000000..4f6500180 --- /dev/null +++ b/dashboard/components/icons/HyperLinkIcon.tsx @@ -0,0 +1,22 @@ +import { SVGProps } from 'react'; + +const HyperLinkIcon = (props: SVGProps) => ( + + + +); + +export default HyperLinkIcon; diff --git a/dashboard/components/inventory/components/InventorySidePanel.tsx b/dashboard/components/inventory/components/InventorySidePanel.tsx index 4b56a8723..f0fe05ea3 100644 --- a/dashboard/components/inventory/components/InventorySidePanel.tsx +++ b/dashboard/components/inventory/components/InventorySidePanel.tsx @@ -1,3 +1,6 @@ +import SidepanelHeader from '@components/sidepanel/SidepanelHeader'; +import SidepanelPage from '@components/sidepanel/SidepanelPage'; +import Pill from '@components/pill/Pill'; import formatNumber from '../../../utils/formatNumber'; import providers from '../../../utils/providerHelper'; import Button from '../../button/Button'; @@ -27,6 +30,7 @@ type InventorySidePanelProps = { isOpen: boolean; bulkItems: [] | string[]; updateBulkTags: (action?: 'delete' | undefined) => void; + tabs: string[]; }; function InventorySidePanel({ @@ -43,80 +47,125 @@ function InventorySidePanel({ deleteLoading, isOpen, bulkItems, - updateBulkTags + updateBulkTags, + tabs }: InventorySidePanelProps) { + const getLastFetched = (date: string) => { + const givenDate = new Date(date); + // Current date + const currentDate = new Date(); + + // Calculate "since last month" date + const lastMonthDate = new Date(currentDate); + lastMonthDate.setMonth(currentDate.getMonth() - 1); + + // Calculate "since last week" date + const lastWeekDate = new Date(currentDate); + lastWeekDate.setDate(currentDate.getDate() - 7); + + let message; + // Compare the given date to the calculated dates + if (givenDate > lastMonthDate) { + message = 'Since last month'; + } else if (givenDate > lastWeekDate) { + message = 'Since last week'; + } else { + message = 'More than a month ago'; + } + return message; + }; + return ( <> {/* Modal headers */} -
- {data && ( -
- - {data.provider} - - + {data && ( + + {!data && bulkItems && (
-

- {data.service} +

+ Managing tags for {formatNumber(bulkItems.length)}{' '} + {bulkItems.length > 1 ? 'resources' : 'resource'}

-

- {data.name} - - - - - +

+ All actions will overwrite previous tags for these resources

-
- )} - {!data && bulkItems && ( -
-

- Managing tags for {formatNumber(bulkItems.length)}{' '} - {bulkItems.length > 1 ? 'resources' : 'resource'} -

-

- All actions will overwrite previous tags for these resources -

-
- )} - -
- -
-
+ )} + + )} {/* Tabs */} - + + {/* Tab Content */} + {tabs.includes('resource details') && ( + +
+
+

+ Cloud account +

+

+ {!data && ( +

+ )} + {data && {data.account}} +

+
+
+

+ Region +

+

+ {!data && ( +

+ )} + {data && {data.region}} +

+
+
+

+ Cost +

+

+ {!data && ( +

+ )} + {data && {data?.cost.toFixed(2)}$} + {data && ( + + {getLastFetched(data.fetchedAt)} + + )} +

+
+
+

+ Relations +

+

+ {!data && ( +

+ )} + {data && ( + {data.relations.length} related resources + )} +

+
+
+
+ )} {/* Tags form */} -
- {page === 'tags' && ( + {tabs.includes('tags') && ( +
{ e.preventDefault(); @@ -127,7 +176,7 @@ function InventorySidePanel({ updateTags(); } }} - className="flex flex-col gap-6 pt-2" + className="flex flex-col gap-6 px-1 pt-2" > {tags && tags.map((tag, id) => ( @@ -185,8 +234,9 @@ function InventorySidePanel({
- )} - + + )} +
{page === 'delete' && ( <>
diff --git a/dashboard/components/inventory/hooks/useInventory/types/useInventoryTypes.ts b/dashboard/components/inventory/hooks/useInventory/types/useInventoryTypes.ts index cf4eb2b09..544133e08 100644 --- a/dashboard/components/inventory/hooks/useInventory/types/useInventoryTypes.ts +++ b/dashboard/components/inventory/hooks/useInventory/types/useInventoryTypes.ts @@ -40,6 +40,7 @@ export type Tag = { }; export type InventoryItem = { + relations: any[]; account: string; accountId: string; cost: number; @@ -55,7 +56,7 @@ export type InventoryItem = { service: string; tags: Tag[] | [] | null; }; -export type Pages = 'tags' | 'delete'; +export type Pages = 'resource details' | 'tags' | 'delete'; export type View = { id: number; diff --git a/dashboard/components/inventory/hooks/useInventory/useInventory.tsx b/dashboard/components/inventory/hooks/useInventory/useInventory.tsx index aace2643b..ceb6fb4ba 100644 --- a/dashboard/components/inventory/hooks/useInventory/useInventory.tsx +++ b/dashboard/components/inventory/hooks/useInventory/useInventory.tsx @@ -34,7 +34,7 @@ function useInventory() { const [shouldFetchMore, setShouldFetchMore] = useState(false); const [isOpen, setIsOpen] = useState(false); const [data, setData] = useState(); - const [page, setPage] = useState('tags'); + const [page, setPage] = useState('resource details'); const [tags, setTags] = useState(); const [loading, setLoading] = useState(false); const [deleteLoading, setDeleteLoading] = useState(false); @@ -461,7 +461,7 @@ function useInventory() { */ function cleanModal() { setData(undefined); - setPage('tags'); + setPage('resource details'); } /** Opens the modal, as well as: diff --git a/dashboard/pages/inventory.tsx b/dashboard/pages/inventory.tsx index 5a9d6451b..4ef3017a4 100644 --- a/dashboard/pages/inventory.tsx +++ b/dashboard/pages/inventory.tsx @@ -173,6 +173,7 @@ export default function Inventory() { deleteLoading={deleteLoading} bulkItems={bulkItems} updateBulkTags={updateBulkTags} + tabs={['resource details', 'tags']} /> {/* Error state */} diff --git a/dashboard/services/settingsService.ts b/dashboard/services/settingsService.ts index 5b4b5dd50..59d48e463 100644 --- a/dashboard/services/settingsService.ts +++ b/dashboard/services/settingsService.ts @@ -135,6 +135,19 @@ const settingsService = { } }, + async getResourceById(urlParams: string) { + try { + const res = await fetch( + `${BASE_URL}/resources${urlParams}`, + settings('GET') + ); + const data = await res.json(); + return data; + } catch (error) { + return Error; + } + }, + async getInventoryStats() { try { const res = await fetch(`${BASE_URL}/stats`, settings('GET')); From c16d190a181ca01b3299d949f4c45d9256d0fbbd Mon Sep 17 00:00:00 2001 From: Mohammed Faisal Hussain Date: Tue, 31 Oct 2023 20:06:57 +0530 Subject: [PATCH 4/4] fix: optimized code and fixed date issue --- .../components/explorer/DependencyGraph.tsx | 4 +- .../components/InventorySidePanel.tsx | 21 ++----- dashboard/components/pill/Pill.tsx | 63 ++++++++++--------- .../components/sidepanel/SidepanelTabs.tsx | 4 +- dashboard/utils/formatString.ts | 7 +++ 5 files changed, 51 insertions(+), 48 deletions(-) create mode 100644 dashboard/utils/formatString.ts diff --git a/dashboard/components/explorer/DependencyGraph.tsx b/dashboard/components/explorer/DependencyGraph.tsx index 336113065..5b650d767 100644 --- a/dashboard/components/explorer/DependencyGraph.tsx +++ b/dashboard/components/explorer/DependencyGraph.tsx @@ -64,9 +64,7 @@ const DependencyGraph = ({ data }: DependencyGraphProps) => { const handleNodeClick = async (event: EventObject) => { const nodeData = event.target.data(); settingsService.getResourceById(`?resourceId=${nodeData.id}`).then(res => { - if (res === Error) { - console.log('Error retrieving resource by id'); - } else { + if (res !== Error) { openModal(res); } }); diff --git a/dashboard/components/inventory/components/InventorySidePanel.tsx b/dashboard/components/inventory/components/InventorySidePanel.tsx index f0fe05ea3..8248657cd 100644 --- a/dashboard/components/inventory/components/InventorySidePanel.tsx +++ b/dashboard/components/inventory/components/InventorySidePanel.tsx @@ -51,23 +51,14 @@ function InventorySidePanel({ tabs }: InventorySidePanelProps) { const getLastFetched = (date: string) => { - const givenDate = new Date(date); - // Current date - const currentDate = new Date(); - - // Calculate "since last month" date - const lastMonthDate = new Date(currentDate); - lastMonthDate.setMonth(currentDate.getMonth() - 1); - - // Calculate "since last week" date - const lastWeekDate = new Date(currentDate); - lastWeekDate.setDate(currentDate.getDate() - 7); - + const dateLastFetched = new Date(date); + const today = new Date(); + const aMonthAgo = new Date(today.getFullYear(), today.getMonth() - 1, today.getDate()); + const aWeekAgo = new Date(today.getFullYear(), today.getMonth(), today.getDate() - 7); let message; - // Compare the given date to the calculated dates - if (givenDate > lastMonthDate) { + if (dateLastFetched > aMonthAgo) { message = 'Since last month'; - } else if (givenDate > lastWeekDate) { + } else if (dateLastFetched > aWeekAgo) { message = 'Since last week'; } else { message = 'More than a month ago'; diff --git a/dashboard/components/pill/Pill.tsx b/dashboard/components/pill/Pill.tsx index 90ee465f5..31718ff2d 100644 --- a/dashboard/components/pill/Pill.tsx +++ b/dashboard/components/pill/Pill.tsx @@ -14,40 +14,45 @@ export type PillProps = { }; function Pill({ status, children, textcase = 'lowercase' }: PillProps) { - function handleBgColor() { - let color; - - if (status === 'active') color = 'bg-green-100'; - if (status === 'pending') color = 'bg-orange-100'; - if (status === 'removed') color = 'bg-rose-100'; - if (status === 'inactive') color = 'bg-zinc-100'; - if (status === 'info') color = 'bg-blue-100'; - if (status === 'new') color = 'bg-sky-100'; - if (status === 'highlight') color = 'bg-violet-100'; - - return color; - } - - function handleFontColor() { - let color; - - if (status === 'active') color = 'text-green-400'; - if (status === 'pending') color = 'text-orange-400'; - if (status === 'removed') color = 'text-red-400'; - if (status === 'inactive') color = 'text-zinc-400'; - if (status === 'info') color = 'text-blue-500'; - if (status === 'new') color = 'text-teal-600'; - if (status === 'highlight') color = 'text-violet-600'; - - return color; - } + const colors = { + active: { + background: 'bg-green-100', + text: 'text-green-400' + }, + pending: { + background: 'bg-orange-100', + text: 'text-orange-400' + }, + removed: { + background: 'bg-rose-100', + text: 'text-red-400' + }, + inactive: { + background: 'bg-zinc-100', + text: 'text-zinc-400' + }, + info: { + background: 'bg-blue-100', + text: 'text-blue-500' + }, + new: { + background: 'bg-sky-100', + text: 'text-teal-600' + }, + highlight: { + background: 'bg-violet-100', + text: 'text-violet-600' + } + }; + + const handleColor = () => colors[status]; return (

{children}

diff --git a/dashboard/components/sidepanel/SidepanelTabs.tsx b/dashboard/components/sidepanel/SidepanelTabs.tsx index 453789fb4..b5715b3a6 100644 --- a/dashboard/components/sidepanel/SidepanelTabs.tsx +++ b/dashboard/components/sidepanel/SidepanelTabs.tsx @@ -1,3 +1,5 @@ +import { capitalizeString } from "@utils/formatString"; + export type SidepanelTabsProps = { goTo: (page: any) => void; page: string; @@ -19,7 +21,7 @@ function SidepanelTabs({ goTo, page, tabs }: SidepanelTabsProps) { : 'border-transparent hover:text-komiser-700' }`} > - {tab.charAt(0).toUpperCase() + tab.slice(1).toLowerCase()}{' '} + {capitalizeString(tab)}{' '} {/* capitalize first letter */} diff --git a/dashboard/utils/formatString.ts b/dashboard/utils/formatString.ts new file mode 100644 index 000000000..a698374f6 --- /dev/null +++ b/dashboard/utils/formatString.ts @@ -0,0 +1,7 @@ +export function capitalizeString(inputString: string) { + if (inputString.length < 0) return inputString; + + return ( + inputString.charAt(0).toUpperCase() + inputString.slice(1).toLowerCase() + ); +} \ No newline at end of file