Skip to content

Commit ec025df

Browse files
authored
Merge pull request #18 from dwarfered/15-export-to-csv-button
csv export for msgraph assigned approles
2 parents 4e6d018 + 9092d79 commit ec025df

File tree

3 files changed

+152
-0
lines changed

3 files changed

+152
-0
lines changed

components/ExportCSVButton.tsx

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// components/ExportCSVButton.tsx
2+
import React from 'react';
3+
import { Button } from '@fluentui/react-components';
4+
import { makeStyles } from '@fluentui/react-components';
5+
import { exportToCSV } from '@/lib/utils/csvExporter';
6+
7+
interface ExportCSVButtonProps<T extends object> {
8+
data: T[];
9+
fileName?: string;
10+
disabled?: boolean;
11+
buttonText?: string;
12+
}
13+
14+
const useStyles = makeStyles({
15+
buttonContainer: {
16+
display: 'flex',
17+
justifyContent: 'flex-end',
18+
margin: '16px 0',
19+
},
20+
});
21+
22+
const ExportCSVButton = <T extends object>({
23+
data,
24+
fileName = 'data.csv',
25+
disabled = false,
26+
buttonText = 'Export to CSV',
27+
}: ExportCSVButtonProps<T>) => {
28+
const styles = useStyles();
29+
30+
const handleExport = () => {
31+
exportToCSV<T>(data, fileName);
32+
};
33+
34+
return (
35+
<div className={styles.buttonContainer}>
36+
<Button
37+
onClick={handleExport}
38+
disabled={disabled}
39+
appearance="primary"
40+
size="small"
41+
shape="square"
42+
>
43+
{buttonText}
44+
</Button>
45+
</div>
46+
);
47+
};
48+
49+
export default ExportCSVButton;

components/enterprise-applications/app-role-permissions-grid/AppRolePermissionsGrid.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { appRoleColumns } from "./AppRolePermissionsGrid.columns";
3434
import { findEntraOpsClassificationByPermission } from "@/lib/utils/entraOpsHelper";
3535
import { SkeletonGrid } from "@/components/SkeletonGrid";
3636
import useDebounce from "@/lib/utils/common";
37+
import ExportCSVButton from "@/components/ExportCSVButton";
3738

3839
function useAuthenticatedSWR<T>(url: string, isAuthenticated: boolean) {
3940
return useSWR<ODataResponse<T>>(isAuthenticated ? url : null, fetcher);
@@ -193,6 +194,8 @@ export default function AppRolePermissionsGrid() {
193194
/>
194195
</div>
195196

197+
<ExportCSVButton<GridItem> data={filteredItems} disabled={filteredItems.length === 0} fileName="appRolePermissions.csv" />
198+
196199
<DataGrid
197200
size="small"
198201
items={filteredItems}

lib/utils/csvExporter.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
export function convertToCSV<T extends object>(data: T[]): string {
2+
if (!data || data.length === 0) {
3+
return "";
4+
}
5+
6+
const headers = Object.keys(data[0]) as Array<keyof T>;
7+
8+
const csvRows = [
9+
headers.join(","),
10+
...data.map((row) =>
11+
headers
12+
.map((fieldName) => {
13+
const value = row[fieldName];
14+
let cell: string;
15+
16+
if (value === null || value === undefined) {
17+
cell = "";
18+
} else if (typeof value !== "string") {
19+
cell = String(value);
20+
} else {
21+
cell = value;
22+
}
23+
24+
cell = cell.replace(/"/g, '""');
25+
26+
if (cell.search(/("|,|\n)/g) >= 0) {
27+
cell = `"${cell}"`;
28+
}
29+
30+
return cell;
31+
})
32+
.join(",")
33+
),
34+
];
35+
36+
return csvRows.join("\n");
37+
}
38+
39+
function getCurrentTimestamp(): string {
40+
const now = new Date();
41+
42+
const year = now.getFullYear().toString().padStart(4, "0");
43+
const month = (now.getMonth() + 1).toString().padStart(2, "0");
44+
const day = now.getDate().toString().padStart(2, "0");
45+
46+
const hours = now.getHours().toString().padStart(2, "0");
47+
const minutes = now.getMinutes().toString().padStart(2, "0");
48+
const seconds = now.getSeconds().toString().padStart(2, "0");
49+
50+
const milliseconds = now
51+
.getMilliseconds()
52+
.toString()
53+
.padStart(3, "0")
54+
.substring(0, 2);
55+
56+
return `${year}${month}${day}${hours}${minutes}${seconds}${milliseconds}`;
57+
}
58+
59+
function appendTimestampToFileName(
60+
fileName: string,
61+
timestamp: string
62+
): string {
63+
const lastDotIndex = fileName.lastIndexOf(".");
64+
65+
if (lastDotIndex === -1) {
66+
return `${fileName}${timestamp}`;
67+
}
68+
69+
const namePart = fileName.substring(0, lastDotIndex);
70+
const extensionPart = fileName.substring(lastDotIndex); // Includes the dot
71+
72+
return `${namePart}${timestamp}${extensionPart}`;
73+
}
74+
75+
export function downloadCSV(
76+
csvContent: string,
77+
fileName: string = "data.csv"
78+
): void {
79+
const timestamp = getCurrentTimestamp();
80+
const timestampedFileName = appendTimestampToFileName(fileName, timestamp);
81+
const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
82+
const url = URL.createObjectURL(blob);
83+
const link = document.createElement("a");
84+
link.href = url;
85+
link.setAttribute("download", timestampedFileName);
86+
87+
document.body.appendChild(link);
88+
link.click();
89+
90+
document.body.removeChild(link);
91+
URL.revokeObjectURL(url);
92+
}
93+
94+
export function exportToCSV<T extends object>(
95+
data: T[],
96+
fileName: string = "data.csv"
97+
) {
98+
const csv = convertToCSV(data);
99+
downloadCSV(csv, fileName);
100+
}

0 commit comments

Comments
 (0)