Skip to content

Commit

Permalink
feat: add Programs page to UI and seed program tags (UnlockedLabs#471)
Browse files Browse the repository at this point in the history
  • Loading branch information
CK-7vn authored Oct 31, 2024
1 parent eede11e commit de2ff1b
Show file tree
Hide file tree
Showing 7 changed files with 333 additions and 145 deletions.
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

0 comments on commit de2ff1b

Please sign in to comment.