diff --git a/csm_web/frontend/src/components/App.tsx b/csm_web/frontend/src/components/App.tsx
index 6799ce7c..1c321f7a 100644
--- a/csm_web/frontend/src/components/App.tsx
+++ b/csm_web/frontend/src/components/App.tsx
@@ -8,6 +8,7 @@ import { emptyRoles, Roles } from "../utils/user";
import CourseMenu from "./CourseMenu";
import Home from "./Home";
import Policies from "./Policies";
+import { DataExport } from "./data_export/DataExport";
import { EnrollmentMatcher } from "./enrollment_automation/EnrollmentMatcher";
import { Resources } from "./resource_aggregation/Resources";
import Section from "./section/Section";
@@ -39,6 +40,7 @@ const App = () => {
} />
} />
} />
+ } />
} />
diff --git a/csm_web/frontend/src/components/AutoGrid.tsx b/csm_web/frontend/src/components/AutoGrid.tsx
new file mode 100644
index 00000000..43f0bfb1
--- /dev/null
+++ b/csm_web/frontend/src/components/AutoGrid.tsx
@@ -0,0 +1,48 @@
+import React from "react";
+
+import "../css/base/autogrid.scss";
+
+interface AutoColumnsProps {
+ children?: React.ReactNode[];
+}
+
+/**
+ * Automatically format children in balanced columns.
+ */
+export const AutoGrid = ({ children }: AutoColumnsProps) => {
+ const gridSize = Math.ceil(Math.sqrt(children?.length ?? 0));
+
+ if (children == null) {
+ return null;
+ }
+
+ const raw_table: React.ReactNode[][] = [];
+ children.forEach((item, idx) => {
+ if (idx % gridSize == 0) {
+ raw_table.push([item]);
+ } else {
+ raw_table[raw_table.length - 1].push(item);
+ }
+ });
+
+ // transpose table
+ const table = raw_table[0].map((_, colIndex) => raw_table.map(row => row[colIndex]));
+
+ return (
+
+
+
+ {table.map((row, rowIdx) => (
+
+ {row.map((item, itemIdx) => (
+
+ {item}
+ |
+ ))}
+
+ ))}
+
+
+
+ );
+};
diff --git a/csm_web/frontend/src/components/Home.tsx b/csm_web/frontend/src/components/Home.tsx
index b8f0298f..da7873e0 100644
--- a/csm_web/frontend/src/components/Home.tsx
+++ b/csm_web/frontend/src/components/Home.tsx
@@ -8,6 +8,7 @@ import { useCourses } from "../utils/queries/courses";
import { Profile, Course, Role } from "../utils/types";
import LoadingSpinner from "./LoadingSpinner";
+import FileExport from "../../static/frontend/img/file-export.svg";
import PlusIcon from "../../static/frontend/img/plus.svg";
import scssColors from "../css/base/colors-export.module.scss";
@@ -17,6 +18,7 @@ const Home = () => {
const { data: courses, isSuccess: coursesLoaded, isError: coursesLoadError } = useCourses();
let content = null;
+ let headingRight = null;
if (profilesLoaded && coursesLoaded) {
// loaded, no error
const coursesById: Map = new Map();
@@ -52,6 +54,17 @@ const Home = () => {
})}
);
+
+ const isCoordinator = profiles!.some(profile => profile.role === Role.COORDINATOR);
+
+ if (isCoordinator) {
+ headingRight = (
+
+
+ Export
+
+ );
+ }
} else if (profilesLoadError) {
// error during load
content = Profiles not found
;
@@ -66,11 +79,14 @@ const Home = () => {
return (
-
My courses
-
-
- Add Course
-
+
+
My courses
+
+
+ Add Course
+
+
+
{headingRight}
{content}
diff --git a/csm_web/frontend/src/components/course/Course.tsx b/csm_web/frontend/src/components/course/Course.tsx
index a46a3f14..e1d20cbc 100644
--- a/csm_web/frontend/src/components/course/Course.tsx
+++ b/csm_web/frontend/src/components/course/Course.tsx
@@ -6,7 +6,6 @@ import { useCourseSections } from "../../utils/queries/courses";
import { Course as CourseType } from "../../utils/types";
import LoadingSpinner from "../LoadingSpinner";
import { CreateSectionModal } from "./CreateSectionModal";
-import { DataExportModal } from "./DataExportModal";
import { SectionCard } from "./SectionCard";
import { SettingsModal } from "./SettingsModal";
import { WhitelistModal } from "./WhitelistModal";
@@ -27,7 +26,6 @@ const DAY_OF_WEEK_ABREVIATIONS: { [day: string]: string } = Object.freeze({
});
const COURSE_MODAL_TYPE = Object.freeze({
- exportData: "csv",
createSection: "mksec",
whitelist: "whitelist",
settings: "settings"
@@ -83,9 +81,7 @@ const Course = ({ courses, priorityEnrollment, enrollmentTimes }: CourseProps):
* Render the currently chosen modal.
*/
const renderModal = (): React.ReactElement | null => {
- if (whichModal == COURSE_MODAL_TYPE.exportData) {
- return setShowModal(false)} />;
- } else if (whichModal == COURSE_MODAL_TYPE.createSection) {
+ if (whichModal == COURSE_MODAL_TYPE.createSection) {
return (
Create Section
-