Skip to content

Commit

Permalink
finish first draft
Browse files Browse the repository at this point in the history
  • Loading branch information
wkim10 committed Dec 11, 2024
1 parent f5dd5de commit 6a50cf9
Show file tree
Hide file tree
Showing 6 changed files with 438 additions and 110 deletions.
Binary file added public/empty_list.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 7 additions & 2 deletions src/app/api/user/route.client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { User, VolunteerDetails } from "@prisma/client";
import { Role, User, VolunteerDetails } from "@prisma/client";

type CreateUserInput = Omit<User, "id" | "events" | "eventIds">;
type CreateVolunteerDetailsInput = Omit<
Expand Down Expand Up @@ -47,8 +47,13 @@ export const getUser = async (userID: string) => {
export const getUserByEmail = async (email: string) => {
const url = `/api/user?email=${email}`;
return fetchApi(url, "GET");
};

export const getUsersByRole = async (role: Role) => {
const url = `/api/user?role=${role}`;
return fetchApi(url, "GET");
};

}
export const deleteUser = async (userID: string) => {
const url = `/api/user?id=${userID}`;
return fetchApi(url, "DELETE");
Expand Down
47 changes: 45 additions & 2 deletions src/app/api/user/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ export const POST = async (request: NextRequest) => {
},
});

await prisma.code.create({
data: {
codeString: "",
expire: new Date(),
userId: savedUser.id,
},
});

return NextResponse.json({
code: "SUCCESS",
message: `User created with email: ${savedUser.email}`,
Expand Down Expand Up @@ -68,6 +76,10 @@ export const DELETE = async (request: NextRequest) => {
}

try {
await prisma.code.delete({
where: { userId: id },
});

await prisma.volunteerDetails.delete({
where: { userId: id },
});
Expand Down Expand Up @@ -99,9 +111,9 @@ export const GET = async (request: NextRequest) => {
const { searchParams } = new URL(request.url);
const id: string | undefined = searchParams.get("id") || undefined;
const email: string | undefined = searchParams.get("email") || undefined;
const role: string | undefined = searchParams.get("role") || undefined;

// Check if id and email is null
if (!id && !email) {
if (!id && !email && !role) {
return NextResponse.json(
{
code: "BAD_REQUEST",
Expand All @@ -110,6 +122,37 @@ export const GET = async (request: NextRequest) => {
{ status: 400 }
);
}

if (role) {
try {
const users = await prisma.user.findMany({
where: { role: role === "ADMIN" ? "ADMIN" : "VOLUNTEER" },
include: { volunteerDetails: true },
});

if (!users || users.length === 0) {
return NextResponse.json(
{
code: "NOT_FOUND",
message: "No users found",
},
{ status: 404 }
);
}

return NextResponse.json({
code: "SUCCESS",
data: users,
});
} catch (error) {
console.error("Error:", error);
return NextResponse.json({
code: "ERROR",
message: error,
});
}
}

try {
const fetchedUser = await prisma.user.findUnique({
where: id ? { id } : { email },
Expand Down
127 changes: 123 additions & 4 deletions src/app/private/volunteers/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,131 @@
"use client";

import VolunteerTable from "@components/VolunteerTable/VolunteerTable";
import SearchBar from "@components/SearchBar";
import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";
import { Icon } from "@iconify/react/dist/iconify.js";
import { Button } from "@mui/material";
import React from "react";
import { Role, User } from "@prisma/client";
import { deleteUser, getUsersByRole } from "@api/user/route.client";
import Image from "next/image";

export default function VolunteersPage() {
const [users, setUsers] = React.useState<User[]>();
const [selected, setSelected] = React.useState<string[]>([]);
const [searchText, setSearchText] = React.useState("");

React.useEffect(() => {
const fetchUsers = async () => {
try {
const response = await getUsersByRole(Role.VOLUNTEER);
setUsers(response.data);
} catch (error) {
console.error("Error fetching volunteers:", error);
}
};

fetchUsers();
}, []);

// Filter users based on the search text
const filteredUsers = users?.filter(
(user) =>
user.firstName.toLowerCase().includes(searchText.toLowerCase()) ||
user.lastName.toLowerCase().includes(searchText.toLowerCase()) ||
user.email.toLowerCase().includes(searchText.toLowerCase())
);

const deleteUsers = async (selectedIds: string[]) => {
try {
const deletePromises = selectedIds.map((id) => deleteUser(id));
const responses = await Promise.all(deletePromises);
const allDeleted = responses.every(
(response) => response.code === "SUCCESS"
);

if (allDeleted) {
setUsers((prevUsers) =>
prevUsers
? prevUsers.filter((user) => !selectedIds.includes(user.id))
: []
);
setSelected([]);
console.log("All users deleted successfully", responses);
} else {
console.error("Not all deletions succeeded");
}
} catch (error) {
console.error("Error deleting users:", error);
}
};

return (
<div>
<h1> Volunteer Home</h1>
<SearchBar Title="hi" Subtext="yo" />
<VolunteerTable showPagination={true}></VolunteerTable>
<div className="flex flex-col gap-8">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<Icon icon="mdi:people" width="44" height="44" />
<div className="text-4xl font-['Kepler_Std'] font-semibold">
Volunteer List ({users ? users.length : 0})
</div>
</div>
{selected.length > 0 ? (
<div className="flex items-center gap-4">
<div>{selected.length} Selected</div>
<Button
sx={{
display: "flex",
padding: "10px 18px",
alignItems: "center",
gap: "8px",
borderRadius: "8px",
backgroundColor: "var(--Rose-600, #E61932)",
color: "white",
fontWeight: 600,
textTransform: "none",
"&:hover": {
backgroundColor: "var(--Rose-700, #C11429)",
},
}}
onClick={() => deleteUsers(selected)}
>
<DeleteOutlineIcon sx={{ width: 20, color: "whitesmoke" }} />
<div>Delete</div>
</Button>
</div>
) : (
<div className="h-[44.5px]"></div>
)}
</div>
<SearchBar
onSearchChange={(value) => {
setSearchText(value);
setSelected([]);
}}
/>
{filteredUsers && filteredUsers.length > 0 ? (
<VolunteerTable
showPagination={true}
fromVolunteerPage
users={filteredUsers}
selected={selected}
setSelected={setSelected}
/>
) : (
<div className="text-center">
<div className="relative w-full h-[50vh]">
<Image
src="/empty_list.png"
alt="Empty List"
layout="fill"
objectFit="contain"
/>
</div>
<div className="text-[#344054] font-['Kepler_Std'] text-3xl font-semibold mt-8">
No volunteers found!
</div>
</div>
)}
</div>
);
}
55 changes: 38 additions & 17 deletions src/components/SearchBar.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,46 @@
import React from "react";
import { Icon } from "@iconify/react/dist/iconify.js";
import { Box, InputBase } from "@mui/material";
import SearchIcon from "@mui/icons-material/Search";

interface SearchBarProps {
Title: string;
Subtext: number | string;
onSearchChange: (searchText: string) => void;
}

const SearchBar = ({ Title, Subtext }: SearchBarProps) => {
return (
<div className="flex items-start relative">
{/* Main Content Box */}
<div className="w-full flex justify-center flex-col shadow-md h-[44px] w-[480px] items-start gap-[16px] rounded-tr-[8px] rounded-tl-[8px] rounded-br-[8px] rounded-bl-[8px] border border-[#Grey/500] bg-[#FFF] relative">
{/* Close Icon positioned in the top-right corner */}
<div className="flex justify-start">
<Icon icon="material-symbols-light:search" width="24" height="24" style={{ color: 'grey/500' }} />
</div>
</div>
</div>
);
const SearchBar = ({ onSearchChange }: SearchBarProps) => {
return (
<Box
sx={{
display: "flex",
padding: "5px 7px",
alignItems: "center",
gap: "8px",
borderRadius: "8px",
border: "1px solid var(--Grey-300, #D0D5DD)",
background: "var(--White, #FFF)",
boxShadow: "0px 1px 2px 0px rgba(16, 24, 40, 0.05)",
width: "100%",
maxWidth: 400,
}}
>
<SearchIcon sx={{ color: "var(--Grey-500, #667085)" }} />
<InputBase
placeholder="Search"
onChange={(e) => onSearchChange(e.target.value)}
sx={{
width: "100%",
fontSize: "14px",
color: "var(--Grey-700, #344054)",
"& input::placeholder": {
color: "var(--Grey-500, #667085)",
fontFamily: "Inter, sans-serif",
fontSize: "16px",
fontWeight: 400,
lineHeight: "24px",
},
}}
/>
</Box>
);
};

export default SearchBar;


Loading

0 comments on commit 6a50cf9

Please sign in to comment.