mirror of
https://github.com/Leado123/Leado123.github.io.git
synced 2026-10-01 00:32:59 +00:00
textbook cost & class name
This commit is contained in:
@@ -16,17 +16,21 @@
|
|||||||
"@apollo/client": "^3.12.6",
|
"@apollo/client": "^3.12.6",
|
||||||
"@nextui-org/select": "^2.4.9",
|
"@nextui-org/select": "^2.4.9",
|
||||||
"@react-pdf/renderer": "^4.2.1",
|
"@react-pdf/renderer": "^4.2.1",
|
||||||
|
"@types/mime-types": "^2.1.4",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"flowbite": "^2.5.2",
|
"flowbite": "^2.5.2",
|
||||||
"flowbite-react": "^0.10.2",
|
"flowbite-react": "^0.10.2",
|
||||||
"gh-pages": "^6.3.0",
|
"gh-pages": "^6.3.0",
|
||||||
"graphql": "^16.10.0",
|
"graphql": "^16.10.0",
|
||||||
|
"mime-types": "^2.1.35",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-datasheet-grid": "^4.11.5",
|
"react-datasheet-grid": "^4.11.5",
|
||||||
"react-doc-viewer": "^0.1.14",
|
"react-doc-viewer": "^0.1.14",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-fast-marquee": "^1.6.5",
|
"react-fast-marquee": "^1.6.5",
|
||||||
|
"react-pro-sidebar": "^1.1.0",
|
||||||
"react-router-dom": "^7.1.3",
|
"react-router-dom": "^7.1.3",
|
||||||
|
"react-select": "^5.10.0",
|
||||||
"tailwindcss": "^3.4.17"
|
"tailwindcss": "^3.4.17"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
+54
-29
@@ -1,16 +1,17 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { DataSheetGrid, textColumn, checkboxColumn, Column, keyColumn, intColumn } from 'react-datasheet-grid';
|
import { DataSheetGrid, textColumn, checkboxColumn, Column, keyColumn, intColumn } from 'react-datasheet-grid';
|
||||||
import "react-datasheet-grid/dist/style.css";
|
import "react-datasheet-grid/dist/style.css";
|
||||||
import { ClassListing } from "./syllabus_comp";
|
import { ClassListing, TextbookCost } from "./syllabus_comp";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import Select from 'react-select';
|
||||||
|
|
||||||
interface PreviewButtonProps {
|
interface PreviewButtonProps {
|
||||||
fileName: string;
|
fileName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PreviewButton = ({ fileName }: PreviewButtonProps) => {
|
const PreviewButton = ({ fileName }: PreviewButtonProps) => {
|
||||||
|
|
||||||
const handlePreview = () => {
|
const handlePreview = () => {
|
||||||
const url = `/#/view/${encodeURIComponent(fileName)}`;
|
const url = `#/view/${encodeURIComponent(fileName)}`;
|
||||||
window.open(url, '_blank');
|
window.open(url, '_blank');
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -21,7 +22,11 @@ const PreviewButton = ({ fileName }: PreviewButtonProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function Admin() {
|
function Admin() {
|
||||||
|
const { highlightedSyllabusId } = useParams<{ highlightedSyllabusId?: string }>();
|
||||||
|
|
||||||
const [username, setUsername] = useState("");
|
const [username, setUsername] = useState("");
|
||||||
const [referenceDatabase, setReferenceDatabase] = useState<ClassListing[]>([]);
|
const [referenceDatabase, setReferenceDatabase] = useState<ClassListing[]>([]);
|
||||||
const [wholeDatabase, setWholeDatabase] = useState<ClassListing[]>([]);
|
const [wholeDatabase, setWholeDatabase] = useState<ClassListing[]>([]);
|
||||||
@@ -32,39 +37,62 @@ function Admin() {
|
|||||||
title: "Preview",
|
title: "Preview",
|
||||||
},
|
},
|
||||||
{ ...keyColumn("reviewed", checkboxColumn), title: "Approved" },
|
{ ...keyColumn("reviewed", checkboxColumn), title: "Approved" },
|
||||||
{ ...keyColumn("className", textColumn), title: "Class Name" },
|
{ ...keyColumn("className", textColumn), title: "Class Abbrev" },
|
||||||
|
{ ...keyColumn("fullClassName", textColumn), title: "Class Name" },
|
||||||
|
{ ...keyColumn("textbookCost", textColumn), title: "free, cheap, moderate, expensive" },
|
||||||
{ ...keyColumn("description", textColumn), title: "Description" },
|
{ ...keyColumn("description", textColumn), title: "Description" },
|
||||||
{ ...keyColumn("professor", textColumn), title: "Instructor" },
|
{ ...keyColumn("professor", textColumn), title: "Instructor" },
|
||||||
{ ...keyColumn("length", intColumn), title: "Length" },
|
{ ...keyColumn("classLength", intColumn), title: "Length" },
|
||||||
{ ...keyColumn("createdByName", textColumn), title: "Submitted By" },
|
{ ...keyColumn("createdByName", textColumn), title: "Submitted By" },
|
||||||
{ ...keyColumn("createdByEmail", textColumn), title: "Email" },
|
{ ...keyColumn("createdByEmail", textColumn), title: "Email" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log(`highlightedSyllabusId is: ${highlightedSyllabusId}`);
|
||||||
|
if (highlightedSyllabusId != null) fetchWholeDatabase();
|
||||||
|
}, [highlightedSyllabusId]);
|
||||||
|
|
||||||
const fetchWholeDatabase = async () => {
|
const fetchWholeDatabase = async () => {
|
||||||
|
if (!highlightedSyllabusId) {
|
||||||
try {
|
try {
|
||||||
let formdata = new FormData();
|
let formdata = new FormData();
|
||||||
formdata.append("username", username);
|
formdata.append("username", username);
|
||||||
var response = await fetch(`https://api.sharesyllabus.me/admin/wholedatabase`, {
|
const response = await fetch(`https://api.sharesyllabus.me/admin/wholedatabase`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: formdata,
|
body: formdata,
|
||||||
});
|
});
|
||||||
var data = await response.json();
|
const data = await response.json();
|
||||||
console.log(data);
|
console.log(data);
|
||||||
setWholeDatabase(data);
|
setWholeDatabase(data);
|
||||||
setReferenceDatabase(data);
|
setReferenceDatabase(data);
|
||||||
} catch {
|
} catch (error) {
|
||||||
console.log("error");
|
console.log("error", error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
console.log(highlightedSyllabusId);
|
||||||
|
const response = await fetch(`https://api.sharesyllabus.me/syllabus/${highlightedSyllabusId}`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
console.log(`data is: ${data}`);
|
||||||
|
setWholeDatabase([...wholeDatabase, data]);
|
||||||
|
setReferenceDatabase([...referenceDatabase, data]);
|
||||||
|
} catch (error) {
|
||||||
|
console.log("error", error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const changeServerDatabase = async () => {
|
const changeServerDatabase = async () => {
|
||||||
try {
|
try {
|
||||||
databaseChanges.forEach(async (change) => {
|
for (const change of databaseChanges) {
|
||||||
|
const { fileName, ...sendableChange } = change;
|
||||||
|
|
||||||
|
const response = await fetch(`https://api.sharesyllabus.me/admin/change/`, {
|
||||||
const {fileName, ...sendableChange} = change;
|
|
||||||
|
|
||||||
var response = await fetch(`https://api.sharesyllabus.me/admin/change/`, {
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
@@ -74,22 +102,18 @@ function Admin() {
|
|||||||
id: sendableChange.id,
|
id: sendableChange.id,
|
||||||
data: sendableChange,
|
data: sendableChange,
|
||||||
})
|
})
|
||||||
}).then((res) => {
|
});
|
||||||
if (res.ok) {
|
|
||||||
console.log("success");
|
if (!response.ok) {
|
||||||
|
console.log("error");
|
||||||
|
}
|
||||||
|
}
|
||||||
fetchWholeDatabase();
|
fetchWholeDatabase();
|
||||||
alert("success!");
|
alert("success!");
|
||||||
} else {
|
} catch (error) {
|
||||||
console.log("error");
|
console.log("error", error);
|
||||||
}
|
}
|
||||||
})
|
};
|
||||||
|
|
||||||
console.log(response);
|
|
||||||
});
|
|
||||||
} catch {
|
|
||||||
console.log("error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const changes = wholeDatabase.filter((item, index) => {
|
const changes = wholeDatabase.filter((item, index) => {
|
||||||
@@ -101,13 +125,14 @@ function Admin() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center w-full min-h-screen p-4">
|
<div className="flex flex-col items-center justify-center w-full min-h-screen p-4">
|
||||||
|
<div>{highlightedSyllabusId}</div>
|
||||||
<div className="text-black mb-4 w-full flex">
|
<div className="text-black mb-4 w-full flex">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Username"
|
placeholder="Username"
|
||||||
value={username}
|
value={username}
|
||||||
onChange={(e) => setUsername(e.target.value)}
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
onKeyDown={ (e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === "Enter") {
|
if (e.key === "Enter") {
|
||||||
fetchWholeDatabase();
|
fetchWholeDatabase();
|
||||||
}
|
}
|
||||||
@@ -133,7 +158,7 @@ function Admin() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-1 w-full h-full overflow-hidden">
|
<div className="flex flex-1 w-full h-full overflow-hidden">
|
||||||
<div className=" w-full h-full ">
|
<div className="w-full h-full">
|
||||||
<DataSheetGrid
|
<DataSheetGrid
|
||||||
className="w-full h-full"
|
className="w-full h-full"
|
||||||
value={wholeDatabase}
|
value={wholeDatabase}
|
||||||
|
|||||||
+5
-2
@@ -16,6 +16,9 @@ export const classOptions = [ // TODO REMOVE
|
|||||||
{ key: "physics", label: "Physics" },
|
{ key: "physics", label: "Physics" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
//TODO: MAKE when you scroll to bottom of page, adds more results
|
||||||
|
//TODO: make it so that when you type in stuff, it automatically searches on the server, but there is a tick rate for responses
|
||||||
|
|
||||||
var searchQuery = "";
|
var searchQuery = "";
|
||||||
|
|
||||||
function findIfMobile() {
|
function findIfMobile() {
|
||||||
@@ -179,7 +182,7 @@ function App() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="listing">
|
<div className="listing">
|
||||||
{syllabi.map((syllabus, i) => {
|
{syllabi.map((syllabus, i) => {
|
||||||
return <SyllabusListing key={i} fileName={syllabus.fileName} className={syllabus.className} description={syllabus.description} classLength={syllabus.classLength} professor={syllabus.professor} />
|
return <SyllabusListing key={i} fileName={syllabus.fileName} className={syllabus.className} description={syllabus.description} fullClassName={syllabus.fullClassName} textbookCost={syllabus.textbookCost} classLength={syllabus.classLength} professor={syllabus.professor} />
|
||||||
})}
|
})}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -209,7 +212,7 @@ function App() {
|
|||||||
|
|
||||||
<div className="listing">
|
<div className="listing">
|
||||||
{syllabi.map((syllabus, i) => {
|
{syllabi.map((syllabus, i) => {
|
||||||
return <SyllabusListing key={i} fileName={syllabus.fileName} className={syllabus.className} description={syllabus.description} classLength={syllabus.classLength} professor={syllabus.professor} />
|
return <SyllabusListing key={i} fileName={syllabus.fileName} className={syllabus.className} fullClassName={syllabus.fullClassName} textbookCost={syllabus.textbookCost} description={syllabus.description} classLength={syllabus.classLength} professor={syllabus.professor} />
|
||||||
})}
|
})}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
|
||||||
|
function Flag() {
|
||||||
|
|
||||||
|
const params = useParams();
|
||||||
|
const syllabusId = params.id;
|
||||||
|
|
||||||
|
const [reportTitle, setReportTitle] = useState("");
|
||||||
|
const [reportBody, setReportBody] = useState("");
|
||||||
|
const [reportBy, setReportBy] = useState("");
|
||||||
|
|
||||||
|
function submitReport() {
|
||||||
|
fetch(`https://api.sharesyllabus.me/report/${syllabusId}`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
reportType: "syllabus",
|
||||||
|
reportTitle,
|
||||||
|
reportBody,
|
||||||
|
reportBy,
|
||||||
|
syllabusId
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
if (data.msg) {
|
||||||
|
alert(data.msg);
|
||||||
|
} else {
|
||||||
|
alert("Report submitted successfully");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
alert("An error occurred while submitting the report");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full h-full p-8 ">
|
||||||
|
<div className="w-full h-full space-y-6 bg-zinc-950 shadow-lg flex flex-col shadow-black p-8 rounded-lg">
|
||||||
|
<nav>
|
||||||
|
<text className="text-4xl font-semibold flex items-center justify-center align-middle">🚩 File a report on a Syllabus</text>
|
||||||
|
<text className="text-gray-400 text-xs">syllabus: {syllabusId}</text>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div className="space-y-6 flex-1 flex flex-col text-left">
|
||||||
|
<div className="flex justify-center items-center">
|
||||||
|
<text className="flex-1">Title of Report:</text>
|
||||||
|
<button onClick={submitReport} className="justify-center rounded-md p-2 flex font-bold bg-blue-700">Submit Report<span className="material-symbols-rounded pl-2">mail</span></button>
|
||||||
|
</div>
|
||||||
|
<input className="w-full p-2 rounded-lg" value={reportTitle} placeholder="Enter the title of your report" onChange={(e) => {setReportTitle(e.target.value)}}></input>
|
||||||
|
<text className="pt-1 pb-1">Email (OPTIONAL: to get in touch):</text>
|
||||||
|
<input className="w-full p-2 rounded-lg" value={reportBy} placeholder="example@company.com" onChange={(e) => setReportBy(e.target.value)}></input>
|
||||||
|
<text className="pt-1 pb-1">Details:</text>
|
||||||
|
<textarea className="w-full p-2 flex-1 rounded-lg text-black" value={reportBody} placeholder="What is wrong with this syllabus?: incorrect class for syllabus, bad description...." onChange={(e) => setReportBody(e.target.value)}></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Flag;
|
||||||
+171
@@ -0,0 +1,171 @@
|
|||||||
|
import mime from "mime-types";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { ClassListing } from "./syllabus_comp";
|
||||||
|
|
||||||
|
export interface SyllabusReport {
|
||||||
|
id: string;
|
||||||
|
reportTitle: string;
|
||||||
|
reportBody: string;
|
||||||
|
reportBy: string;
|
||||||
|
reportDate: string;
|
||||||
|
syllabusId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Reports() {
|
||||||
|
|
||||||
|
const [username, setUsername] = useState("");
|
||||||
|
const [reports, setReports] = useState<SyllabusReport[]>([]);
|
||||||
|
|
||||||
|
function obtainReports() {
|
||||||
|
fetch("https://api.sharesyllabus.me/admin/getreports", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ username }),
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
console.log(data);
|
||||||
|
setReports(data);
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const [currentReport, setCurrentReport] = useState<SyllabusReport | null>(null);
|
||||||
|
const [currentSyllabus, setCurrentSyllabus] = useState<ClassListing | null>(null);
|
||||||
|
const [currentFileName, setCurrentFileName] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentSyllabus == null || currentSyllabus?.mimeType == null) return;
|
||||||
|
setCurrentFileName(`${currentSyllabus?.id}.${mime.extension(currentSyllabus.mimeType)}`)
|
||||||
|
}, [currentSyllabus])
|
||||||
|
|
||||||
|
function viewReport(sId: string, report: SyllabusReport) {
|
||||||
|
setCurrentReport(report);
|
||||||
|
fetch(`https://api.sharesyllabus.me/admin/getsyllabus/${sId}`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ username }),
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
console.log(data);
|
||||||
|
setCurrentSyllabus(data);
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function previewSyllabus() {
|
||||||
|
const url = `#/view/${encodeURIComponent(currentFileName)}`;
|
||||||
|
window.open(url, '_blank');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function editSyllabusEntry() {
|
||||||
|
const url = `#/admin/${currentSyllabus?.id}`;
|
||||||
|
window.open(url, '_blank');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center w-full min-h-screen p-4">
|
||||||
|
<div className="text-black mb-4 w-full flex">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Username"
|
||||||
|
value={username}
|
||||||
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
obtainReports()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="p-2 border border-gray-300 rounded"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={obtainReports}
|
||||||
|
className="ml-2 p-2 bg-blue-500 text-white rounded"
|
||||||
|
>
|
||||||
|
Obtain Reports
|
||||||
|
</button>
|
||||||
|
<div className="flex-1 flex items-center justify-end">
|
||||||
|
<span className="text-white">
|
||||||
|
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 w-full h-full">
|
||||||
|
<div className=" w-full h-full grid grid-cols-4 gap-4">
|
||||||
|
{reports.map((report, i) => <ReportComponent report={report} viewReport={viewReport} key={i} />)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="w-full bg-zinc-950 grid grid-cols-2 p-4 rounded-md">
|
||||||
|
<div className="flex flex-col gap-2 text-left">
|
||||||
|
<text className="font-bold">
|
||||||
|
{currentReport?.reportTitle}
|
||||||
|
</text>
|
||||||
|
<text>
|
||||||
|
{currentReport?.reportBody}
|
||||||
|
</text>
|
||||||
|
</div>
|
||||||
|
<div className="bg-white p-4 rounded-md text-black flex">
|
||||||
|
<div className="flex flex-1 flex-col text-left">
|
||||||
|
<text className="font-bold">
|
||||||
|
{currentSyllabus?.className}
|
||||||
|
</text>
|
||||||
|
<text>
|
||||||
|
{currentSyllabus?.professor}
|
||||||
|
</text>
|
||||||
|
<text>
|
||||||
|
{currentSyllabus?.description}{currentSyllabus?.fileName}
|
||||||
|
</text>
|
||||||
|
<text>
|
||||||
|
classLength: {currentSyllabus?.classLength} wk
|
||||||
|
</text>
|
||||||
|
</div>
|
||||||
|
<div className="flex-col flex gap-2">
|
||||||
|
<button className="aspect-square bg-blue-600 p-2 rounded-md text-yellow-300" onClick={previewSyllabus}><span className="material-symbols-rounded">open_in_new</span></button>
|
||||||
|
<button className="flex-1 bg-yellow-300 p-2 rounded-md flex items-center" onClick={editSyllabusEntry}><span className="material-symbols-rounded">edit</span></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div >
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReportComponentProps {
|
||||||
|
report: SyllabusReport;
|
||||||
|
viewReport: (sId: string, report: SyllabusReport) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ReportComponent({ report, viewReport }: ReportComponentProps) {
|
||||||
|
const date = new Date(report.reportDate).toLocaleDateString("en-US");
|
||||||
|
|
||||||
|
const view = (e: React.MouseEvent<HTMLDivElement, MouseEvent>): void => {
|
||||||
|
e.stopPropagation();
|
||||||
|
viewReport(report.syllabusId, report);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-white text-black p-2 rounded-md text-left flex flex-col hover:bg-gray-200" onClick={view}>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<text className="font-bold text-lg">{report.reportTitle}</text>
|
||||||
|
<text className="font-light text-md text-gray-500 flex-1">{date}</text>
|
||||||
|
<button className="shadow-lg rounded-md aspect-square text-white bg-black p-1 items-center flex hover:bg-red-600"><span className="material-symbols-rounded text-md">delete</span></button>
|
||||||
|
</div>
|
||||||
|
<text>{report.reportBody}</text>
|
||||||
|
<text className="text-xs font-light">{report.reportBy}</text>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Reports;
|
||||||
@@ -16,6 +16,7 @@ function View() {
|
|||||||
|
|
||||||
const { file } = useParams();
|
const { file } = useParams();
|
||||||
|
|
||||||
|
// TODO: make it work on mobile (pdf viewer doesen't work)
|
||||||
|
|
||||||
const isPDF = checkIfPdf(file || "");
|
const isPDF = checkIfPdf(file || "");
|
||||||
|
|
||||||
|
|||||||
+11
-12
@@ -1,22 +1,21 @@
|
|||||||
import { createRoot } from 'react-dom/client'
|
import { createRoot } from 'react-dom/client';
|
||||||
import { Route, Routes, HashRouter } from 'react-router-dom';
|
import { Route, Routes, HashRouter } from 'react-router-dom';
|
||||||
import './index.css'
|
import './index.css';
|
||||||
import App from './App.tsx'
|
import App from './App.tsx';
|
||||||
import View from './View.tsx'
|
import View from './View.tsx';
|
||||||
import Admin from './Admin.tsx'
|
import Admin from './Admin.tsx';
|
||||||
|
import Flag from './Flag.tsx';
|
||||||
|
import Reports from './Reports.tsx';
|
||||||
|
|
||||||
|
|
||||||
createRoot(document.getElementById('root')!).render(
|
createRoot(document.getElementById('root')!).render(
|
||||||
|
|
||||||
|
|
||||||
<HashRouter>
|
<HashRouter>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<App />} />
|
<Route path="/" element={<App />} />
|
||||||
<Route path="/view/:file" element={<View />} />
|
<Route path="/view/:file" element={<View />} />
|
||||||
<Route path="/admin" element={<Admin />} />
|
<Route path="/admin" element={<Admin />} />
|
||||||
|
<Route path="/admin/:highlightedSyllabusId" element={<Admin />} />
|
||||||
|
<Route path="/flag/:id" element={<Flag />} />
|
||||||
|
<Route path="/reports" element={<Reports />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</HashRouter>
|
</HashRouter>
|
||||||
|
);
|
||||||
)
|
|
||||||
|
|||||||
+70
-5
@@ -2,6 +2,16 @@ import "./syllabuslisting.css";
|
|||||||
import "./App.tsx";
|
import "./App.tsx";
|
||||||
|
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
|
export enum TextbookCost {
|
||||||
|
free,
|
||||||
|
cheap,
|
||||||
|
moderate,
|
||||||
|
expensive
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: implement fullClassName and textbook cost in mobile
|
||||||
|
|
||||||
export interface ClassListing {
|
export interface ClassListing {
|
||||||
className: string;
|
className: string;
|
||||||
@@ -12,13 +22,35 @@ export interface ClassListing {
|
|||||||
reviewed?: boolean;
|
reviewed?: boolean;
|
||||||
createdByName?: string;
|
createdByName?: string;
|
||||||
createdByEmail?: string;
|
createdByEmail?: string;
|
||||||
|
mimeType?: string;
|
||||||
|
textbookCost?: string;
|
||||||
|
fullClassName?: string;
|
||||||
id?: string;
|
id?: string;
|
||||||
}
|
}
|
||||||
function findIfMobile() {
|
function findIfMobile() {
|
||||||
//return true;
|
//return true;
|
||||||
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
||||||
}
|
}
|
||||||
function SyllabusListing({ className, professor, fileName, description, classLength }: ClassListing) {
|
function SyllabusListing({ className, professor, fileName, description, classLength, fullClassName, textbookCost }: ClassListing) {
|
||||||
|
|
||||||
|
const [hideMore, setHideMore] = useState(true);
|
||||||
|
|
||||||
|
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||||
|
|
||||||
|
const id = fileName.split(".")[0];
|
||||||
|
|
||||||
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
|
if (buttonRef.current && !buttonRef.current.contains(event.target as Node)) {
|
||||||
|
setHideMore(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.addEventListener('mousedown', handleClickOutside);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousedown', handleClickOutside);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@@ -26,21 +58,54 @@ function SyllabusListing({ className, professor, fileName, description, classLen
|
|||||||
navigate(`/view/${encodeURIComponent(`${fileName}`)}`);
|
navigate(`/view/${encodeURIComponent(`${fileName}`)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleFlag() {
|
||||||
|
navigate(`/flag/${encodeURIComponent(`${id}`)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const textbookCostElement = () => {
|
||||||
|
if (textbookCost === "free") {
|
||||||
|
return <span className="text-xs text-green-500 font-bold">🤑 Free</span>;
|
||||||
|
} else if (textbookCost === "cheap") {
|
||||||
|
return <span className="text-xs text-yellow-400 font-bold">💸 Cheap</span>;
|
||||||
|
} else if (textbookCost === "moderate") {
|
||||||
|
return <span className="text-xs text-orange-600 font-bold">💵 Moderate</span>;
|
||||||
|
} else {
|
||||||
|
return <span className="text-xs text-red-600 font-bold">💰 Expensive</span>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (!findIfMobile()) {
|
if (!findIfMobile()) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="w-full flex space-x-4 bg-white text-black rounded-lg p-3">
|
<div className="w-full flex space-x-4 bg-white text-black rounded-lg p-3" >
|
||||||
<div className="flex text-left flex-col w-1/4">
|
<div className="flex text-left flex-col w-1/4">
|
||||||
<span className="font-extrabold">{className}</span>
|
<span className="font-extrabold">{className}</span>
|
||||||
|
<span className="text-xs text-gray-600">{fullClassName}</span>
|
||||||
|
{textbookCostElement()}
|
||||||
<span className="flex-1 italic">{professor}</span>
|
<span className="flex-1 italic">{professor}</span>
|
||||||
{classLength > 7 ? <span className="text-xs font-light text-green-600">{classLength} weeks course (normal)</span> : <span className="text-xs font-light text-red-600">{classLength} weeks course</span>}
|
{classLength > 7 ? <span className="text-xs font-light text-green-600">{classLength} weeks course</span> : <span className="text-xs font-light text-red-600">{classLength} weeks course</span>}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 flex flex-col text-center">
|
<div className="flex-1 flex flex-col text-center">
|
||||||
<span className="w-3/4 font-serif">"{description}"</span>
|
<span className="w-3/4 font-serif">"{description}"</span>
|
||||||
</div>
|
</div>
|
||||||
<button className="aspect-square flex items-center align-middle justify-center rounded-lg hover:bg-slate-200 text-black" onClick={handleView}><span className="material-symbols-rounded text-2xl">open_in_new</span></button>
|
<div className="flex flex-col">
|
||||||
|
<button className="aspect-square flex items-center align-middle justify-center hover:bg-slate-200 rounded-sm text-black" onClick={handleView}>
|
||||||
|
<span className="material-symbols-rounded flex-1 text-2xl">open_in_new</span>
|
||||||
|
</button>
|
||||||
|
<span className="flex-1"></span>
|
||||||
|
<button className="aspect-square flex items-center align-middle justify-center hover:bg-slate-200 rounded-sm text-black" onClick={() => setHideMore(!hideMore)}>
|
||||||
|
<span className="material-symbols-rounded text-2xl">more_horiz</span>
|
||||||
|
<div className="relative" hidden={hideMore}>
|
||||||
|
<button ref={buttonRef} onClick={handleFlag} className="absolute border-black border-2 -left-14 top-2 drop-shadow-lg bg-white rounded-lg aspect-square p-4">
|
||||||
|
<span className="material-symbols-rounded text-red-600 text-2xl">flag</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
@@ -52,7 +117,7 @@ function SyllabusListing({ className, professor, fileName, description, classLen
|
|||||||
<span className=" font-extrabold">{className}</span>
|
<span className=" font-extrabold">{className}</span>
|
||||||
<span className="flex-1 italic">{professor}</span>
|
<span className="flex-1 italic">{professor}</span>
|
||||||
<span className="w-4/5 font-serif">"{description}"</span>
|
<span className="w-4/5 font-serif">"{description}"</span>
|
||||||
{classLength > 7 ? <span className="text-xs font-light text-green-600">{classLength} weeks course (normal)</span> : <span className="text-xs font-light text-red-600">{classLength} weeks course</span>}
|
{classLength > 7 ? <span className="text-xs font-light text-green-600">{classLength} weeks course</span> : <span className="text-xs font-light text-red-600">{classLength} weeks course</span>}
|
||||||
</div>
|
</div>
|
||||||
<button className="flex items-center h-full p-2 rounded-lg bg-slate-200 text-black" onClick={handleView}>
|
<button className="flex items-center h-full p-2 rounded-lg bg-slate-200 text-black" onClick={handleView}>
|
||||||
<span className="material-symbols-rounded text-2xl">open_in_new</span>
|
<span className="material-symbols-rounded text-2xl">open_in_new</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user