Skip to content

Commit

Permalink
feat: add new organization modal (#718)
Browse files Browse the repository at this point in the history
Signed-off-by: inimaz <93inigo93@gmail.com>
  • Loading branch information
inimaz authored Nov 23, 2024
1 parent d466949 commit 6592ddd
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 9 deletions.
79 changes: 79 additions & 0 deletions webapp/src/components/createOrganizationModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import GeneralModal from "./ui/modal";
import { createOrganization } from "@/server-functions/organizations";
import { Separator } from "./ui/separator";
import { Input } from "./ui/input";
import { Organization } from "@/types/organization";

interface ModalProps {
isOpen: boolean;
onClose: () => void;
onOrganizationCreated: () => Promise<void>;
}
interface CreateOrganizationInput {
name: string;
description: string;
}

const CreateOrganizationModal: React.FC<ModalProps> = ({
isOpen,
onClose,
onOrganizationCreated,
}) => {
const initialData: CreateOrganizationInput = {
name: "",
description: "",
};
const initialSavedData: Organization = {
name: "",
id: "",
description: "",
};
const handleSave = async (data: CreateOrganizationInput) => {
const newOrganization: Organization = await createOrganization(data);
await onOrganizationCreated(); // Call the callback to refresh the project list
return newOrganization;
};

const renderForm = (data: CreateOrganizationInput, setData: any) => (
<div>
<h2 className="text-xl font-bold mb-4">Create new organization</h2>
<Separator className="mb-4" />
<Input
type="text"
value={data.name}
onChange={(e) => setData({ ...data, name: e.target.value })}
placeholder="Organization Name"
className={"mt-4 mb-4"}
/>
<Input
type="text"
value={data.description}
onChange={(e) =>
setData({ ...data, description: e.target.value })
}
placeholder="Organization Description"
className={"mt-4 mb-4"}
/>
</div>
);
const renderSavedData = (data: Organization, setSavedData: any) => (
<div>
<h2 className="text-xl font-bold mb-4">
Organization {data.name} Created
</h2>
</div>
);
return (
<GeneralModal
isOpen={isOpen}
onClose={onClose}
onSave={handleSave}
initialData={initialData}
initialSavedData={initialSavedData}
renderForm={renderForm}
renderSavedData={renderSavedData}
/>
);
};

export default CreateOrganizationModal;
1 change: 0 additions & 1 deletion webapp/src/components/experiment-bar-chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ export default function ExperimentsBarChart({
};
useEffect(() => {
const fetchData = async () => {
console.log("Fetching experiments report data");
const data = await getProjectEmissionsByExperiment(
params.projectId,
params.startDate,
Expand Down
52 changes: 45 additions & 7 deletions webapp/src/components/navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ import {
SelectTrigger,
SelectValue,
} from "./ui/select";
import CreateOrganizationModal from "./createOrganizationModal";
import { getOrganizations } from "@/server-functions/organizations";
import { Button } from "./ui/button";

export default function NavBar({
orgs,
Expand All @@ -36,6 +39,11 @@ export default function NavBar({
const [selectedOrg, setSelectedOrg] = useState<string | null>(null);
const iconStyles = "h-4 w-4 flex-shrink-0 text-muted-foreground";
const pathname = usePathname();
const [isNewOrgModalOpen, setNewOrgModalOpen] = useState(false);
const [organizationList, setOrganizationList] = useState<
Organization[] | undefined
>([]);
const [isDropdownOpen, setDropdownOpen] = useState(false);

useEffect(() => {
if (pathname.includes("/members")) {
Expand All @@ -50,21 +58,27 @@ export default function NavBar({
}
}, [pathname, orgs]);

useEffect(() => {
if (orgs) {
setOrganizationList(orgs);
}
}, [orgs]);

useEffect(() => {
if (!selectedOrg) {
try {
const localOrg = localStorage.getItem("organizationId");
if (localOrg) {
setSelectedOrg(localOrg);
} else if (orgs && orgs.length > 0) {
} else if (organizationList && organizationList.length > 0) {
// Set the first organization as the default
setSelectedOrg(orgs[0].id);
setSelectedOrg(organizationList[0].id);
}
} catch (error) {
console.error("Error reading from localStorage:", error);
}
}
}, [selectedOrg, orgs]); // Empty dependency array, runs only on mount
}, [selectedOrg, organizationList]);

// Effect for updating localStorage when selectedOrg changes
useEffect(() => {
Expand All @@ -80,6 +94,17 @@ export default function NavBar({
}
}, [selectedOrg]);

const handleNewOrgClick = async () => {
setNewOrgModalOpen(true);
setDropdownOpen(false); // Close the dropdown menu
};

const refreshOrgList = async () => {
// Fetch the updated list of organizations from the server
const orgs = await getOrganizations();
setOrganizationList(orgs);
};

return (
<div className="flex-1 p-8">
<nav className="flex flex-col h-full font-medium text-sm text-muted-foreground">
Expand Down Expand Up @@ -142,6 +167,8 @@ export default function NavBar({
setSheetOpened(false);
router.push(`/${value}`);
}}
open={isDropdownOpen} // Control the dropdown visibility
onOpenChange={setDropdownOpen} // Update the state when the dropdown is opened/closed
>
<SelectTrigger
className={cn(
Expand All @@ -159,21 +186,27 @@ export default function NavBar({
isCollapsed && "hidden",
)}
>
{orgs &&
orgs.find(
{organizationList &&
organizationList.find(
(org) =>
org.id === selectedOrg,
)?.name}
</span>
</SelectValue>
</SelectTrigger>
<SelectContent>
<Button
onClick={handleNewOrgClick}
variant="ghost"
>
+ Add new organization
</Button>
<SelectGroup>
<SelectLabel className="text-sm font-medium text-muted-foreground">
Organizations
</SelectLabel>
{orgs &&
orgs.map((org) => (
{organizationList &&
organizationList.map((org) => (
<SelectItem
key={org.id}
value={org.id}
Expand All @@ -187,6 +220,11 @@ export default function NavBar({
</SelectContent>
</Select>
)}
<CreateOrganizationModal
isOpen={isNewOrgModalOpen}
onClose={() => setNewOrgModalOpen(false)}
onOrganizationCreated={refreshOrgList}
/>
<NavItem
isSelected={selected === "profile"}
onClick={() => {
Expand Down
1 change: 0 additions & 1 deletion webapp/src/components/runs-scatter-chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ export default function RunsScatterChart({
);
useEffect(() => {
const fetchData = async () => {
console.log("Fetching runs report data");
const data = await getRunEmissionsByExperiment(
params.experimentId,
params.startDate,
Expand Down
33 changes: 33 additions & 0 deletions webapp/src/server-functions/organizations.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Organization } from "@/types/organization";
import { OrganizationReport } from "@/types/organization-report";
import { DateRange } from "react-day-picker";

Expand Down Expand Up @@ -44,3 +45,35 @@ export async function getDefaultOrgId(): Promise<string | null> {
}
return null;
}
export async function getOrganizations(): Promise<Organization[] | undefined> {
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/organizations`);

if (!res.ok) {
console.warn("error fetching organizations list");
return;
}
try {
const orgs = await res.json();
} catch (err) {
console.warn("error processing organizations list");
}
return;
}

export const createOrganization = async (organization: {
name: string;
description: string;
}): Promise<Organization> => {
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/organizations`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(organization),
},
);
const data = await response.json();
return data;
};

0 comments on commit 6592ddd

Please sign in to comment.