Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Programs page and 'tags' to seed data #471

Merged
merged 2 commits into from
Oct 31, 2024
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
11 changes: 11 additions & 0 deletions backend/seeder/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ func seedTestData(db *gorm.DB) {
func createFacilityPrograms(db *gorm.DB) ([]models.ProgramSection, error) {
facilities := []models.Facility{}
randNames := []string{"Anger Management", "Substance Abuse Treatment", "AA/NA", "Thinking for a Change", "A New Freedom", "Dog Training", "A New Path", "GED/Hi-SET", "Parenting", "Employment", "Life Skills", "Health and Wellness", "Financial Literacy", "Computer Skills", "Parenting", "Employment", "Life Skills"}
randTags := []string{"Rehabilitation", "Life-Skills", "12-step", "Required", "Recovery", "DV-Requirement", "10-Weeks", "Addiction"}
if err := db.Find(&facilities).Error; err != nil {
return nil, err
}
Expand Down Expand Up @@ -313,6 +314,16 @@ func createFacilityPrograms(db *gorm.DB) ([]models.ProgramSection, error) {
if err := db.Create(&prog[i]).Error; err != nil {
log.Fatalf("Failed to create program: %v", err)
}
numTags := rand.Intn(3) + 1
for j := 0; j < numTags; j++ {
tag := models.ProgramTag{
ProgramID: prog[i].ID,
Value: randTags[rand.Intn(len(randTags))],
}
if err := db.Create(&tag).Error; err != nil {
log.Fatalf("Failed to create tag: %v", err)
}
}
section := models.ProgramSection{
FacilityID: facilities[idx].ID,
ProgramID: prog[i].ID,
Expand Down
2 changes: 1 addition & 1 deletion backend/src/handlers/programs_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
)

func (srv *Server) registerProgramsRoutes() {
srv.Mux.Handle("GET /api/programs", srv.applyAdminMiddleware(srv.handleIndexPrograms))
srv.Mux.Handle("GET /api/programs", srv.applyMiddleware(srv.handleIndexPrograms))
srv.Mux.Handle("GET /api/programs/{id}", srv.applyMiddleware(srv.handleShowProgram))
srv.Mux.Handle("POST /api/programs", srv.applyAdminMiddleware(srv.handleCreateProgram))
srv.Mux.Handle("DELETE /api/programs/{id}", srv.applyAdminMiddleware(srv.handleDeleteProgram))
Expand Down
233 changes: 93 additions & 140 deletions frontend/src/Components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,11 @@ import {
RectangleStackIcon,
TrophyIcon,
UsersIcon,
ArrowRightEndOnRectangleIcon,
SunIcon,
MoonIcon,
UserCircleIcon,
BuildingOffice2Icon
DocumentTextIcon
} from '@heroicons/react/24/solid';
import { useAuth, handleLogout } from '@/useAuth';
import { useAuth } from '@/useAuth';
import ULIComponent from './ULIComponent';
import { Link } from 'react-router-dom';
import ThemeToggle from './ThemeToggle';

export default function Navbar({
isPinned,
onTogglePin
Expand Down Expand Up @@ -60,138 +54,97 @@ export default function Navbar({
<Link to="/" className="mt-16">
<Brand />
</Link>

<div className="h-full">
<ul className="menu h-full flex flex-col justify-between">
<div>
{user?.role == UserRole.Admin ? (
<>
{/* admin view */}
<li className="mt-16">
<Link to="/admin-dashboard">
<ULIComponent icon={HomeIcon} />
Dashboard
</Link>
</li>
<li>
<Link to="/student-management">
<ULIComponent icon={AcademicCapIcon} />
Students
</Link>
</li>
<li>
<Link to="/admin-management">
<ULIComponent icon={UsersIcon} />
Admins
</Link>
</li>
<li>
<Link to="/open-content-management">
<ULIComponent icon={BookOpenIcon} />
Open Content
</Link>
</li>
<li>
<Link to="/resources-management">
<ULIComponent icon={ArchiveBoxIcon} />
Resources
</Link>
</li>
<li>
<Link to="/provider-platform-management">
<ULIComponent
icon={RectangleStackIcon}
/>
Platforms
</Link>
</li>
<li>
<Link to="/facilities-management">
<ULIComponent
icon={BuildingOffice2Icon}
/>
Facilities
</Link>
</li>
</>
) : (
<>
{/* student view */}
<li className="mt-16">
<Link to="/student-dashboard">
<ULIComponent icon={HomeIcon} />
Dashboard
</Link>
</li>
<li>
<Link to="/my-courses">
<ULIComponent icon={BookOpenIcon} /> My
Courses
</Link>
</li>
<li>
<Link to="/my-progress">
<ULIComponent icon={TrophyIcon} /> My
Progress
</Link>
</li>
<li>
<Link to="/open-content">
<ULIComponent icon={BookOpenIcon} />
Open Content
</Link>
</li>
<li>
<Link to="/course-catalog">
<ULIComponent
icon={BuildingStorefrontIcon}
/>
Course Catalog
</Link>
</li>
</>
)}
</div>
<li className="dropdown dropdown-right dropdown-end w-full">
<div tabIndex={0} role="button">
<ULIComponent
icon={UserCircleIcon}
iconClassName={'w-5 h-5'}
/>
{user?.name_first} {user?.name_last}
</div>
<ul
tabIndex={0}
className="dropdown-content menu bg-grey-2 dark:bg-grey-1 rounded-box z-50 w-52 p-2 shadow"
>
<li className="self-center">
<label className="flex cursor-pointer gap-2">
<ULIComponent
icon={SunIcon}
iconClassName={'w-6 h-6'}
/>
<ThemeToggle />
<ULIComponent
icon={MoonIcon}
iconClassName={'w-6 h-6'}
/>
</label>
</li>
<div className="divider mt-0 mb-0"></div>
<li className="self-center">
<button
onClick={() => {
void handleLogout();
}}
>
<ArrowRightEndOnRectangleIcon className="h-4" />
Logout
</button>
</li>
</ul>
</li>
</ul>
</div>
<ul className="menu">
{user && user.role == UserRole.Admin ? (
<>
{/* admin view */}
<li className="mt-16">
<Link to="/admin-dashboard">
<ULIComponent icon={HomeIcon} /> Dashboard
</Link>
</li>
<li>
<Link to="/student-management">
<ULIComponent icon={AcademicCapIcon} />
Students
</Link>
</li>
<li>
<Link to="/admin-management">
<ULIComponent icon={UsersIcon} />
Admins
</Link>
</li>
<li>
<Link to="/open-content-management">
<ULIComponent icon={BookOpenIcon} />
Open Content
</Link>
</li>
<li>
<Link to="/resources-management">
<ULIComponent icon={ArchiveBoxIcon} />
Resources
</Link>
</li>
<li>
<Link to="/provider-platform-management">
<ULIComponent icon={RectangleStackIcon} />
Platforms
</Link>
</li>
<li className="">
<Link to="/course-catalog-admin">
<ULIComponent icon={BuildingStorefrontIcon} />
Course Catalog
</Link>
</li>
<li className="">
<Link to="/programs">
<ULIComponent icon={DocumentTextIcon} />
Programs
</Link>
</li>
</>
) : (
<>
{/* student view */}
<li className="mt-16">
<Link to="/student-dashboard">
<ULIComponent icon={HomeIcon} /> Dashboard
</Link>
</li>
<li className="">
<Link to="/my-courses">
<ULIComponent icon={BookOpenIcon} /> My Courses
</Link>
</li>
<li className="">
<Link to="/my-progress">
<ULIComponent icon={TrophyIcon} /> My Progress
</Link>
</li>
<li>
<Link to="/open-content">
<ULIComponent icon={BookOpenIcon} />
Open Content
</Link>
</li>
<li className="">
<Link to="/course-catalog">
<ULIComponent icon={BuildingStorefrontIcon} />
Course Catalog
</Link>
</li>
<li className="">
<Link to="/programs">
<ULIComponent icon={DocumentTextIcon} />
Programs
</Link>
</li>
</>
)}
</ul>
</div>
);
}
81 changes: 81 additions & 0 deletions frontend/src/Components/ProgramCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { BookmarkIcon } from '@heroicons/react/24/solid';
import { BookmarkIcon as BookmarkIconOutline } from '@heroicons/react/24/outline';
import LightGreenPill from './pill-labels/LightGreenPill';
import { MouseEvent } from 'react';
import { Program, ViewType } from '@/common';
import API from '@/api/api';

export default function ProgramCard({
program,
callMutate,
view
}: {
program: Program;
callMutate: () => void;
view?: ViewType;
}) {
function updateFavorite(e: MouseEvent) {
e.preventDefault();
API.put(`programs/${program.id}/save`, {})
.then(() => {
callMutate();
})
.catch((error) => {
console.log(error);
});
}

const bookmark: JSX.Element = program.is_favorited ? (
<BookmarkIcon className="h-5 text-primary-yellow" />
) : (
<BookmarkIconOutline
className={`h-5 ${view === ViewType.List ? 'text-header-text' : 'text-white'}`}
/>
);

const tagPills = program.tags.map((tag) => (
<LightGreenPill key={tag.id}>{tag.value.toString()}</LightGreenPill>
));

if (view === ViewType.List) {
return (
<a
className="card bg-base-teal body-small p-6 flex flex-row items-center"
href="#"
onClick={(e) => e.preventDefault()}
>
<div className="flex flex-col justify-between gap-3">
<div className="flex flex-row gap-3 items-center">
<div onClick={(e) => updateFavorite(e)}>{bookmark}</div>
<h2>{program.name}</h2>
<p className="body">|</p>
<div className="flex flex-wrap gap-2">{tagPills}</div>
</div>
<p className="body-small h-[1rem] line-clamp-2 overflow-hidden">
{program.description}
</p>
</div>
</a>
);
} else {
return (
<div className="card card-compact bg-base-teal overflow-hidden relative">
<div
className="absolute top-2 right-2 cursor-pointer"
onClick={(e) => updateFavorite(e)}
>
{bookmark}
</div>
<div className="card-body gap-0.2">
<h3 className="card-title text-sm">{program.name}</h3>
<p className="body-small line-clamp-2">
{program.description}
</p>
<div className="flex flex-wrap py-1 mt-2 space-y-2 sm:space-y-0 gap-x-1 gap-y-2">
{tagPills}
</div>
</div>
</div>
);
}
}
Loading
Loading