Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 164 additions & 0 deletions app/files/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
"use client";

import Spinner from "@/components/Spinner";
import { Table, TableBody, TableCell, TableHeader, TableRow } from "@/components/ui/table";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { File, FileType } from "@/models/file";
import { Response } from "@/models/response";
import { useContext, useEffect, useState } from "react";
import { toast } from "react-toastify";

import { AuthContext } from "../Providers";

type FileEntryProps = {
files: File[];
};

const FileEntry = ({ files }: FileEntryProps) => {
return (
<div className="mx-10 my-4 p-2">
<Table>
<TableHeader>
<TableRow>
<TableCell className="text-lg font-medium">Title</TableCell>
<TableCell className="text-lg font-medium">Description</TableCell>
<TableCell className="text-lg font-medium">Version</TableCell>
<TableCell className="text-lg font-medium">File</TableCell>
</TableRow>
</TableHeader>
<TableBody>
{files.length > 0 ? (
<>
{files.map((file) => (
<TableRow key={file.id}>
<TableCell className="text-lg">{file.title}</TableCell>
<TableCell className="text-lg">{file.description}</TableCell>
<TableCell className="text-lg">{file.version}</TableCell>
<TableCell className="text-lg">
<a className="mt-4 w-auto rounded-md bg-blue-700 p-2 text-center shadow-md transition-all hover:bg-blue-600" href={file.fileUrl} target="_blank">
Download
</a>
</TableCell>
</TableRow>
))}
</>
): (
<TableRow>
<TableCell colSpan={4} className="text-center text-lg">No files found</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
);
};

export default function Files() {

const authContext = useContext(AuthContext);

const [loading, setLoading] = useState(true);

const [files, setFiles] = useState<File[]>([] as File[]);

useEffect(() => {

document.title = "Files | Memphis ARTCC";

const fetchFiles = async () => {
const headers = new Map<string, string>();
if (authContext?.isLoggedIn) {
headers.set("Authorization", `Bearer ${localStorage.getItem("accessToken")}`);
}
headers.set("Content-Type", "application/json");
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/files`, {
headers: Object.fromEntries(headers),
});
if (!response.ok) {
const error = await response.json() as Response<string>;
throw new Error(`Error fetching roster:\n${error.message}`);
}
return await response.json() as Response<File[]>;
};

fetchFiles()
.then((response) => {
setFiles(response.data);
setLoading(false);
})
.catch((error) => {
console.log(error);
toast.error(error);
setLoading(false);
});

}, [authContext]);

return (
<div className="w-100 text-center text-white">
<div className="flex flex-row justify-center">
<div className="mb-4 text-center text-3xl">
Files
</div>
</div>
<div className="rounded-2xl bg-gray-700 p-3 shadow-md">
{!loading ? (
<div>
<Tabs defaultValue="Policies">
<TabsList>
<TabsTrigger className="text-lg text-white" value="Policies">Policies</TabsTrigger>
<TabsTrigger className="text-lg text-white" value="SOPs">SOP&apos;s</TabsTrigger>
<TabsTrigger className="text-lg text-white" value="LOAs">LOA&apos;s</TabsTrigger>
<TabsTrigger className="text-lg text-white" value="References">References</TabsTrigger>
<TabsTrigger className="text-lg text-white" value="Miscellaneous">Miscellaneous</TabsTrigger>
<TabsTrigger className="text-lg text-white" value="Clients">Clients</TabsTrigger>
{authContext?.isTrainingStaff() ? (
<TabsTrigger className="text-lg text-white" value="Training Staff">Training Staff</TabsTrigger>
) : <></>}
{authContext?.isStaff() ? (
<TabsTrigger className="text-lg text-white" value="Staff">Staff</TabsTrigger>
) : <></>}
{authContext?.isSeniorStaff() ? (
<TabsTrigger className="text-lg text-white" value="Senior Staff">Senior Staff</TabsTrigger>
) : <></>}
</TabsList>
<TabsContent value="Policies">
<FileEntry files={files.filter((file) => file.type === FileType.POLICY)} />
</TabsContent>
<TabsContent value="SOPs">
<FileEntry files={files.filter((file) => file.type === FileType.SOP)} />
</TabsContent>
<TabsContent value="LOAs">
<FileEntry files={files.filter((file) => file.type === FileType.LOA)} />
</TabsContent>
<TabsContent value="References">
<FileEntry files={files.filter((file) => file.type === FileType.REFERENCE)} />
</TabsContent>
<TabsContent value="Miscellaneous">
<FileEntry files={files.filter((file) => file.type === FileType.MISC)} />
</TabsContent>
<TabsContent value="Clients">
<FileEntry files={files.filter((file) => file.type === FileType.CLIENT)} />
</TabsContent>
{authContext?.isTrainingStaff() ? (
<TabsContent value="Training Staff">
<FileEntry files={files.filter((file) => file.type === FileType.TRAINING_STAFF)} />
</TabsContent>
) : <></>}
{authContext?.isStaff() ? (
<TabsContent value="Staff">
<FileEntry files={files.filter((file) => file.type === FileType.STAFF)} />
</TabsContent>
) : <></>}
{authContext?.isSeniorStaff() ? (
<TabsContent value="Senior Staff">
<FileEntry files={files.filter((file) => file.type === FileType.SENIOR_STAFF)} />
</TabsContent>
) : <></>}
</Tabs>
</div>
) : <Spinner />}
</div>
</div>
);
}
3 changes: 1 addition & 2 deletions app/not-found.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ export default function NotFound() {
404 - Page Not Found
</h2>
<div className="flex flex-col items-center py-4 text-lg">
<button className="w-24 rounded-md bg-sky-800 p-2 text-center text-white shadow-md transition-all hover:bg-sky-700"
onClick={goBack}>
<button className="mt-4 w-auto rounded-md bg-blue-700 p-2 text-center text-white shadow-md transition-all hover:bg-blue-600" onClick={goBack}>
Go Back
</button>
</div>
Expand Down
135 changes: 93 additions & 42 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
"use client";

import Spinner from "@/components/Spinner";
import { Token } from "@/models/auth/token";
import { News } from "@/models/news";
import { Response } from "@/models/response";
import { Stats } from "@/models/stats";
import { format } from "date-fns";
import { Trophy } from "lucide-react";
import { useRouter } from "next/navigation";
import { useContext, useEffect, useState } from "react";
Expand All @@ -18,15 +21,19 @@ export default function Home() {
const [isMember, setIsMember] = useState(false);

const [loadingTop, setLoadingTop] = useState(true);
const [loadingNews, setloadingNews] = useState(true);


const [top, setTop] = useState([] as Stats[]);
const [news, setNews] = useState([] as News[]);

useEffect(() => {
document.title = "Home | Memphis ARTCC";
if (auth?.user) {
setUser(auth.user);
setIsMember(auth.user.isMember);
}

const fetchTopControllers = async () => {
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/stats/top`, {
headers: {
Expand All @@ -40,6 +47,20 @@ export default function Home() {
}
return await response.json() as Response<Stats[]>;
};

const fetchNews = async () => {
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/news`, {
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
const error = await response.json() as Response<string>;
throw new Error(`Error fetching news:\n${error.message}`);
}
return await response.json() as Response<News[]>;
};

fetchTopControllers()
.then((response) => {
setTop(response.data);
Expand All @@ -50,6 +71,17 @@ export default function Home() {
toast.error(error);
setLoadingTop(false);
});

fetchNews()
.then((response) => {
setNews(response.data);
setloadingNews(false);
})
.catch((error) => {
console.log(error);
toast.error(error);
setloadingNews(false);
});
}, [auth]);

return (
Expand All @@ -70,7 +102,7 @@ export default function Home() {
<div className="flex flex-col items-center">
If you would like to become a visitor, please click the button below.
<br />
<button className="mt-4 w-auto rounded-md bg-sky-800 p-2 text-center shadow-md transition-all hover:bg-sky-700"
<button className="mt-4 w-auto rounded-md bg-blue-700 p-2 text-center shadow-md transition-all hover:bg-blue-600"
onClick={() => router.push("/visit")}>
Become a Visitor
</button>
Expand All @@ -82,57 +114,76 @@ export default function Home() {
News
</div>
<div className="rounded-2xl bg-gray-700 p-3 shadow-md">
Testing
{!loadingNews ? (
<>
{news.length > 0 ? (
<>
{news.map((newsEntry) => (
<div key={newsEntry.id} className="mb-2 flex items-center justify-between p-2 text-base text-white">
<div className="flex items-center gap-2">
<span className="rounded-full bg-blue-600 px-3 py-1 text-lg font-semibold text-white">
{newsEntry.author}
</span>
<span className="text-lg text-white">{newsEntry.title}</span>
</div>
<div className="mx-2 h-0.5 flex-1 bg-gray-500"></div>
<span className="text-lg text-white">{format(newsEntry.updated, "EEEE, MMMM do yyyy, h:mm a")}</span>
</div>
))}
</>
) : (
<div className="text-xl">No news found</div>
)}
</>
) : <Spinner />}
</div>
<div className="my-4 text-center text-3xl">
Top Controllers
</div>
<div className="rounded-2xl bg-gray-700 p-3 shadow-md">
{top.length > 0 ? (
<div className="flex items-end justify-center gap-8">
<div className="flex h-24 w-32 flex-col items-center">
<Trophy className="mb-1 size-8 text-gray-400" />
{top[1] ? (
<>
<span className="text-lg">{top[1].firstName} {top[1].lastName}</span>
<span className="text-lg">{top[1].totalHours} hrs</span>
</>
) : (
<span>-</span>
)}
</div>
<div className="flex h-28 w-40 flex-col items-center">
<Trophy className="mb-1 size-10 text-yellow-500" />
{top[0] ? (
<>
<span className="text-lg">{top[0].firstName} {top[0].lastName}</span>
<span className="text-lg">{top[0].totalHours} hrs</span>
</>
) : (
<span>-</span>
)}
</div>
<div className="flex h-24 w-32 flex-col items-center">
<Trophy className="mb-1 size-8 text-orange-600" />
{top[2] ? (
<>
<span className="text-lg">{top[2].firstName} {top[2].lastName}</span>
<span className="text-lg">{top[2].totalHours} hrs</span>
</>
) : (
<span>-</span>
)}
</div>
</div>
) : (
{!loadingTop ? (
<>
{loadingTop ? (
<div className="text-xl">Loading...</div>
{top.length > 0 ? (
<div className="flex items-end justify-center gap-8">
<div className="flex h-24 w-32 flex-col items-center">
<Trophy className="mb-1 size-8 text-gray-400" />
{top[1] ? (
<>
<span className="text-lg">{top[1].firstName} {top[1].lastName}</span>
<span className="text-lg">{top[1].totalHours} hrs</span>
</>
) : (
<span>-</span>
)}
</div>
<div className="flex h-28 w-40 flex-col items-center">
<Trophy className="mb-1 size-10 text-yellow-500" />
{top[0] ? (
<>
<span className="text-lg">{top[0].firstName} {top[0].lastName}</span>
<span className="text-lg">{top[0].totalHours} hrs</span>
</>
) : (
<span>-</span>
)}
</div>
<div className="flex h-24 w-32 flex-col items-center">
<Trophy className="mb-1 size-8 text-orange-600" />
{top[2] ? (
<>
<span className="text-lg">{top[2].firstName} {top[2].lastName}</span>
<span className="text-lg">{top[2].totalHours} hrs</span>
</>
) : (
<span>-</span>
)}
</div>
</div>
) : (
<div className="text-xl">No top controllers found</div>
)}
</>
)}
) : <Spinner />}
</div>
</div>
);
Expand Down
Loading