From 6d477678a962c0ce7e39787e2393250c1e9b112f Mon Sep 17 00:00:00 2001 From: pmoharana-cmd Date: Wed, 24 Apr 2024 21:23:43 -0400 Subject: [PATCH] Demo ready --- backend/entities/resource_entity.py | 2 +- backend/script/reset_demo.py | 4 +- backend/test/services/resource_test_data.py | 232 ++++++++++++++- backend/test/services/service_test.py | 4 +- backend/test/services/service_test_data.py | 237 ++++++++++++++- compass/app/admin/layout.tsx | 2 +- compass/app/api/resource/all/route.ts | 24 ++ compass/app/api/resource/route.ts | 5 - compass/app/api/service/all/route.ts | 24 ++ compass/app/api/service/route.ts | 5 - compass/app/resource/page.tsx | 22 +- compass/app/service/layout.tsx | 10 +- compass/app/service/page.tsx | 25 +- compass/components/Table/ResourceIndex.tsx | 60 ++-- compass/components/Table/ServiceIndex.tsx | 312 ++++++++++++++++++++ compass/components/TagsInput/Index.tsx | 17 +- compass/components/TagsInput/TagsArray.tsx | 4 +- compass/package-lock.json | 79 +++-- compass/package.json | 5 +- compass/utils/models/Resource.ts | 11 + compass/utils/models/Service.ts | 12 + 21 files changed, 975 insertions(+), 121 deletions(-) create mode 100644 compass/app/api/resource/all/route.ts delete mode 100644 compass/app/api/resource/route.ts create mode 100644 compass/app/api/service/all/route.ts delete mode 100644 compass/app/api/service/route.ts create mode 100644 compass/components/Table/ServiceIndex.tsx create mode 100644 compass/utils/models/Resource.ts create mode 100644 compass/utils/models/Service.ts diff --git a/backend/entities/resource_entity.py b/backend/entities/resource_entity.py index c11514b..299decb 100644 --- a/backend/entities/resource_entity.py +++ b/backend/entities/resource_entity.py @@ -26,7 +26,7 @@ class ResourceEntity(EntityBase): # set fields id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.now) - name: Mapped[str] = mapped_column(String(32), nullable=False) + name: Mapped[str] = mapped_column(String(64), nullable=False) summary: Mapped[str] = mapped_column(String(100), nullable=False) link: Mapped[str] = mapped_column(String, nullable=False) program: Mapped[Program_Enum] = mapped_column(Enum(Program_Enum), nullable=False) diff --git a/backend/script/reset_demo.py b/backend/script/reset_demo.py index 7c9f36e..46fc916 100644 --- a/backend/script/reset_demo.py +++ b/backend/script/reset_demo.py @@ -22,6 +22,6 @@ entities.EntityBase.metadata.create_all(engine) with Session(engine) as session: user_test_data.insert_test_data(session) - service_test_data.insert_fake_data(session) - resource_test_data.insert_fake_data(session) + service_test_data.insert_test_data(session) + resource_test_data.insert_test_data(session) session.commit() diff --git a/backend/test/services/resource_test_data.py b/backend/test/services/resource_test_data.py index c20aa59..bb39266 100644 --- a/backend/test/services/resource_test_data.py +++ b/backend/test/services/resource_test_data.py @@ -5,7 +5,7 @@ from ...entities import ResourceEntity from ...models.enum_for_models import ProgramTypeEnum from ...models.resource_model import Resource -resource_1 = Resource( +resource1 = Resource( id=1, name="Resource 1", summary="Helpful information for victims of domestic violence", @@ -14,7 +14,7 @@ resource_1 = Resource( created_at=datetime(2023, 6, 1, 10, 0, 0), ) -resource_2 = Resource( +resource2 = Resource( id=2, name="Resource 2", summary="Legal assistance resources", @@ -23,7 +23,7 @@ resource_2 = Resource( created_at=datetime(2023, 6, 2, 12, 30, 0), ) -resource_3 = Resource( +resource3 = Resource( id=3, name="Resource 3", summary="Financial aid resources", @@ -32,7 +32,7 @@ resource_3 = Resource( created_at=datetime(2023, 6, 3, 15, 45, 0), ) -resource_4 = Resource( +resource4 = Resource( id=4, name="Resource 4", summary="Counseling and support groups", @@ -41,7 +41,7 @@ resource_4 = Resource( created_at=datetime(2023, 6, 4, 9, 15, 0), ) -resource_5 = Resource( +resource5 = Resource( id=5, name="Resource 5", summary="Shelter and housing resources", @@ -50,7 +50,210 @@ resource_5 = Resource( created_at=datetime(2023, 6, 5, 11, 30, 0), ) -resources = [resource_1, resource_2, resource_3, resource_4, resource_5] +resources = [resource1, resource2, resource3, resource4, resource5] + +resource_1 = Resource( + id=1, + name="National Domestic Violence Hotline", + summary="24/7 confidential support for victims of domestic violence", + link="https://www.thehotline.org", + program=ProgramTypeEnum.DOMESTIC, + created_at=datetime(2023, 6, 1, 10, 0, 0), +) + +resource_2 = Resource( + id=2, + name="Legal Aid Society", + summary="Free legal assistance for low-income individuals", + link="https://www.legalaidnyc.org", + program=ProgramTypeEnum.COMMUNITY, + created_at=datetime(2023, 6, 2, 12, 30, 0), +) + +resource_3 = Resource( + id=3, + name="Financial Empowerment Center", + summary="Free financial counseling and education services", + link="https://www1.nyc.gov/site/dca/consumers/get-free-financial-counseling.page", + program=ProgramTypeEnum.ECONOMIC, + created_at=datetime(2023, 6, 3, 15, 45, 0), +) + +resource_4 = Resource( + id=4, + name="National Coalition Against Domestic Violence", + summary="Resources and support for victims of domestic violence", + link="https://ncadv.org", + program=ProgramTypeEnum.DOMESTIC, + created_at=datetime(2023, 6, 4, 9, 15, 0), +) + +resource_5 = Resource( + id=5, + name="Safe Horizon", + summary="Shelter and support services for victims of violence", + link="https://www.safehorizon.org", + program=ProgramTypeEnum.DOMESTIC, + created_at=datetime(2023, 6, 5, 11, 30, 0), +) + +resource_6 = Resource( + id=6, + name="National Sexual Assault Hotline", + summary="24/7 confidential support for survivors of sexual assault", + link="https://www.rainn.org", + program=ProgramTypeEnum.COMMUNITY, + created_at=datetime(2023, 6, 6, 14, 0, 0), +) + +resource_7 = Resource( + id=7, + name="Victim Compensation Fund", + summary="Financial assistance for victims of crime", + link="https://ovc.ojp.gov/program/victim-compensation", + program=ProgramTypeEnum.ECONOMIC, + created_at=datetime(2023, 6, 7, 16, 45, 0), +) + +resource_8 = Resource( + id=8, + name="Battered Women's Justice Project", + summary="Legal and technical assistance for victims of domestic violence", + link="https://www.bwjp.org", + program=ProgramTypeEnum.DOMESTIC, + created_at=datetime(2023, 6, 8, 10, 30, 0), +) + +resource_9 = Resource( + id=9, + name="National Network to End Domestic Violence", + summary="Advocacy and resources for ending domestic violence", + link="https://nnedv.org", + program=ProgramTypeEnum.COMMUNITY, + created_at=datetime(2023, 6, 9, 13, 0, 0), +) + +resource_10 = Resource( + id=10, + name="Economic Justice Project", + summary="Promoting economic security for survivors of domestic violence", + link="https://www.njep.org", + program=ProgramTypeEnum.ECONOMIC, + created_at=datetime(2023, 6, 10, 15, 15, 0), +) + +resource_11 = Resource( + id=11, + name="Domestic Violence Legal Hotline", + summary="Free legal advice for victims of domestic violence", + link="https://www.womenslaw.org/find-help/national/hotlines", + program=ProgramTypeEnum.DOMESTIC, + created_at=datetime(2023, 6, 11, 9, 0, 0), +) + +resource_12 = Resource( + id=12, + name="National Resource Center on Domestic Violence", + summary="Comprehensive information and resources on domestic violence", + link="https://nrcdv.org", + program=ProgramTypeEnum.COMMUNITY, + created_at=datetime(2023, 6, 12, 11, 30, 0), +) + +resource_13 = Resource( + id=13, + name="Financial Assistance for Victims of Crime", + summary="Funding for expenses related to victimization", + link="https://ovc.ojp.gov/program/victim-assistance-funding", + program=ProgramTypeEnum.ECONOMIC, + created_at=datetime(2023, 6, 13, 14, 45, 0), +) + +resource_14 = Resource( + id=14, + name="National Clearinghouse for the Defense of Battered Women", + summary="Legal resources and support for battered women", + link="https://www.ncdbw.org", + program=ProgramTypeEnum.DOMESTIC, + created_at=datetime(2023, 6, 14, 10, 0, 0), +) + +resource_15 = Resource( + id=15, + name="Victim Connect Resource Center", + summary="Referral helpline for crime victims", + link="https://victimconnect.org", + program=ProgramTypeEnum.COMMUNITY, + created_at=datetime(2023, 6, 15, 13, 15, 0), +) + +resource_16 = Resource( + id=16, + name="Economic Empowerment Program", + summary="Financial literacy and job readiness training for survivors", + link="https://www.purplepurse.com", + program=ProgramTypeEnum.ECONOMIC, + created_at=datetime(2023, 6, 16, 16, 30, 0), +) + +resource_17 = Resource( + id=17, + name="National Domestic Violence Law Project", + summary="Legal information and resources for domestic violence survivors", + link="https://www.womenslaw.org", + program=ProgramTypeEnum.DOMESTIC, + created_at=datetime(2023, 6, 17, 9, 45, 0), +) + +resource_18 = Resource( + id=18, + name="Victim Rights Law Center", + summary="Free legal services for victims of sexual assault", + link="https://victimrights.org", + program=ProgramTypeEnum.COMMUNITY, + created_at=datetime(2023, 6, 18, 12, 0, 0), +) + +resource_19 = Resource( + id=19, + name="Financial Justice Project", + summary="Advocating for economic justice for survivors of violence", + link="https://www.financialjusticeproject.org", + program=ProgramTypeEnum.ECONOMIC, + created_at=datetime(2023, 6, 19, 15, 30, 0), +) + +resource_20 = Resource( + id=20, + name="National Center on Domestic and Sexual Violence", + summary="Training and resources to end domestic and sexual violence", + link="http://www.ncdsv.org", + program=ProgramTypeEnum.DOMESTIC, + created_at=datetime(2023, 6, 20, 10, 15, 0), +) + +resources1 = [ + resource_1, + resource_2, + resource_3, + resource_4, + resource_5, + resource_6, + resource_7, + resource_8, + resource_9, + resource_10, + resource_11, + resource_12, + resource_13, + resource_14, + resource_15, + resource_16, + resource_17, + resource_18, + resource_19, + resource_20, +] from sqlalchemy import text from sqlalchemy.orm import Session, DeclarativeBase, InstrumentedAttribute @@ -78,6 +281,23 @@ def reset_table_id_seq( session.execute(sql) +def insert_test_data(session: Session): + """Inserts fake resource data into the test session.""" + global resources1 + # Create entities for test resource data + entities = [] + for resource in resources1: + entity = ResourceEntity.from_model(resource) + session.add(entity) + entities.append(entity) + + # Reset table IDs to prevent ID conflicts + reset_table_id_seq(session, ResourceEntity, ResourceEntity.id, len(resources1) + 1) + + # Commit all changes + session.commit() + + def insert_fake_data(session: Session): """Inserts fake resource data into the test session.""" global resources diff --git a/backend/test/services/service_test.py b/backend/test/services/service_test.py index f0b4131..ab6e6d9 100644 --- a/backend/test/services/service_test.py +++ b/backend/test/services/service_test.py @@ -46,8 +46,8 @@ def test_get_by_program(service_svc: ServiceService): def test_create(service_svc: ServiceService): - service = service_svc.create(user_test_data.admin, service_test_data.service_7) - assert service.name == service_test_data.service_7.name + service = service_svc.create(user_test_data.admin, service_test_data.service7) + assert service.name == service_test_data.service7.name assert isinstance(service, Service) diff --git a/backend/test/services/service_test_data.py b/backend/test/services/service_test_data.py index 5dd49de..a418663 100644 --- a/backend/test/services/service_test_data.py +++ b/backend/test/services/service_test_data.py @@ -5,7 +5,7 @@ from ...entities import ServiceEntity from ...models.enum_for_models import ProgramTypeEnum from ...models.service_model import Service -service_1 = Service( +service1 = Service( id=1, name="service 1", status="open", @@ -14,7 +14,7 @@ service_1 = Service( program=ProgramTypeEnum.COMMUNITY, ) -service_2 = Service( +service2 = Service( id=2, name="service 2", status="closed", @@ -23,7 +23,7 @@ service_2 = Service( program=ProgramTypeEnum.DOMESTIC, ) -service_3 = Service( +service3 = Service( id=3, name="service 3", status="open", @@ -32,7 +32,7 @@ service_3 = Service( program=ProgramTypeEnum.DOMESTIC, ) -service_4 = Service( +service4 = Service( id=4, name="service 4", status="waitlist", @@ -41,7 +41,7 @@ service_4 = Service( program=ProgramTypeEnum.COMMUNITY, ) -service_5 = Service( +service5 = Service( id=5, name="service 5", status="open", @@ -50,7 +50,7 @@ service_5 = Service( program=ProgramTypeEnum.COMMUNITY, ) -service_6 = Service( +service6 = Service( id=6, name="service 6", status="waitlist", @@ -68,7 +68,7 @@ service_6_edit = Service( program=ProgramTypeEnum.ECONOMIC, ) -service_7 = Service( +service7 = Service( id=7, name="service 7", status="waitlist", @@ -86,7 +86,210 @@ new_service = Service( program=ProgramTypeEnum.DOMESTIC, ) -services = [service_1, service_2, service_3, service_4, service_5, service_6] +services = [service1, service2, service3, service6, service5, service6] + +service_1 = Service( + id=1, + name="Crisis Hotline", + status="open", + summary="24/7 support for individuals in crisis", + requirements=["Anonymous", "Confidential"], + program=ProgramTypeEnum.DOMESTIC, +) + +service_2 = Service( + id=2, + name="Shelter Placement", + status="open", + summary="Emergency shelter for victims of domestic violence", + requirements=["Referral required", "Safety assessment"], + program=ProgramTypeEnum.DOMESTIC, +) + +service_3 = Service( + id=3, + name="Legal Advocacy", + status="waitlist", + summary="Legal support and representation for survivors", + requirements=["Intake required", "Income eligibility"], + program=ProgramTypeEnum.COMMUNITY, +) + +service_4 = Service( + id=4, + name="Counseling Services", + status="open", + summary="Individual and group therapy for survivors", + requirements=["Initial assessment", "Insurance accepted"], + program=ProgramTypeEnum.DOMESTIC, +) + +service_5 = Service( + id=5, + name="Financial Assistance", + status="open", + summary="Emergency funds for survivors in need", + requirements=["Application required", "Proof of income"], + program=ProgramTypeEnum.ECONOMIC, +) + +service_6 = Service( + id=6, + name="Housing Assistance", + status="waitlist", + summary="Support for finding safe and affordable housing", + requirements=["Referral required", "Background check"], + program=ProgramTypeEnum.ECONOMIC, +) + +service_7 = Service( + id=7, + name="Job Training", + status="open", + summary="Employment skills training for survivors", + requirements=["Enrollment required", "18+"], + program=ProgramTypeEnum.ECONOMIC, +) + +service_8 = Service( + id=8, + name="Support Groups", + status="open", + summary="Peer support groups for survivors", + requirements=["Registration required", "Confidential"], + program=ProgramTypeEnum.COMMUNITY, +) + +service_9 = Service( + id=9, + name="Children's Services", + status="open", + summary="Specialized services for children exposed to domestic violence", + requirements=["Parental consent", "Age-appropriate"], + program=ProgramTypeEnum.DOMESTIC, +) + +service_10 = Service( + id=10, + name="Safety Planning", + status="open", + summary="Personalized safety planning for survivors", + requirements=["Confidential", "Collaborative"], + program=ProgramTypeEnum.DOMESTIC, +) + +service_11 = Service( + id=11, + name="Community Education", + status="open", + summary="Workshops and training on domestic violence prevention", + requirements=["Open to the public", "Registration preferred"], + program=ProgramTypeEnum.COMMUNITY, +) + +service_12 = Service( + id=12, + name="Healthcare Services", + status="open", + summary="Medical care and support for survivors", + requirements=["Referral required", "Insurance accepted"], + program=ProgramTypeEnum.DOMESTIC, +) + +service_13 = Service( + id=13, + name="Transportation Assistance", + status="waitlist", + summary="Help with transportation for survivors", + requirements=["Eligibility assessment", "Limited availability"], + program=ProgramTypeEnum.ECONOMIC, +) + +service_14 = Service( + id=14, + name="Court Accompaniment", + status="open", + summary="Support and advocacy during court proceedings", + requirements=["Legal case", "Scheduling required"], + program=ProgramTypeEnum.COMMUNITY, +) + +service_15 = Service( + id=15, + name="Relocation Assistance", + status="waitlist", + summary="Support for relocating to a safe environment", + requirements=["Referral required", "Safety assessment"], + program=ProgramTypeEnum.ECONOMIC, +) + +service_16 = Service( + id=16, + name="Parenting Classes", + status="open", + summary="Education and support for parents", + requirements=["Open to parents", "Pre-registration required"], + program=ProgramTypeEnum.COMMUNITY, +) + +service_17 = Service( + id=17, + name="Life Skills Training", + status="open", + summary="Workshops on various life skills for survivors", + requirements=["Enrollment required", "Commitment to attend"], + program=ProgramTypeEnum.ECONOMIC, +) + +service_18 = Service( + id=18, + name="Advocacy Services", + status="open", + summary="Individual advocacy and support for survivors", + requirements=["Intake required", "Confidential"], + program=ProgramTypeEnum.DOMESTIC, +) + +service_19 = Service( + id=19, + name="Volunteer Opportunities", + status="open", + summary="Various volunteer roles supporting the organization", + requirements=["Background check", "Training required"], + program=ProgramTypeEnum.COMMUNITY, +) + +service_20 = Service( + id=20, + name="Referral Services", + status="open", + summary="Referrals to community resources and partner agencies", + requirements=["Intake required", "Based on individual needs"], + program=ProgramTypeEnum.DOMESTIC, +) + +services1 = [ + service_1, + service_2, + service_3, + service_4, + service_5, + service_6, + service_7, + service_8, + service_9, + service_10, + service_11, + service_12, + service_13, + service_14, + service_15, + service_16, + service_17, + service_18, + service_19, + service_20, +] from sqlalchemy import text from sqlalchemy.orm import Session, DeclarativeBase, InstrumentedAttribute @@ -114,6 +317,24 @@ def reset_table_id_seq( session.execute(sql) +def insert_test_data(session: Session): + """Inserts fake organization data into the test session.""" + global services1 + + # Create entities for test organization data + entities = [] + for service in services1: + entity = ServiceEntity.from_model(service) + session.add(entity) + entities.append(entity) + + # Reset table IDs to prevent ID conflicts + reset_table_id_seq(session, ServiceEntity, ServiceEntity.id, len(services1) + 1) + + # Commit all changes + session.commit() + + def insert_fake_data(session: Session): """Inserts fake organization data into the test session.""" global services diff --git a/compass/app/admin/layout.tsx b/compass/app/admin/layout.tsx index 8d00539..78bf6a6 100644 --- a/compass/app/admin/layout.tsx +++ b/compass/app/admin/layout.tsx @@ -42,7 +42,7 @@ export default function RootLayout({ console.log( `Accessed admin page but incorrect permissions: ${user.username} ${user.role}` ); - router.push("/auth/login"); + router.push("/home"); return; } diff --git a/compass/app/api/resource/all/route.ts b/compass/app/api/resource/all/route.ts new file mode 100644 index 0000000..3649278 --- /dev/null +++ b/compass/app/api/resource/all/route.ts @@ -0,0 +1,24 @@ +import Resource from "@/utils/models/Resource"; +import { NextResponse } from "next/server"; + +export async function GET(request: Request) { + const apiEndpoint = `${process.env.NEXT_PUBLIC_API_HOST}/api/resource`; + + console.log(apiEndpoint); + + const { searchParams } = new URL(request.url); + const uuid = searchParams.get("uuid"); + + const data = await fetch(`${apiEndpoint}?user_id=${uuid}`); + + const resourceData: Resource[] = await data.json(); + // TODO: Remove make every resource visible + + const resources = resourceData.map((resource: Resource) => { + resource.visible = true; + + return resource; + }); + + return NextResponse.json(resources, { status: data.status }); +} diff --git a/compass/app/api/resource/route.ts b/compass/app/api/resource/route.ts deleted file mode 100644 index 771f369..0000000 --- a/compass/app/api/resource/route.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { NextResponse } from "next/server"; - -export async function GET() { - return NextResponse.json({ message: "Hello World!" }, { status: 200 }); -} diff --git a/compass/app/api/service/all/route.ts b/compass/app/api/service/all/route.ts new file mode 100644 index 0000000..b164bc1 --- /dev/null +++ b/compass/app/api/service/all/route.ts @@ -0,0 +1,24 @@ +import Service from "@/utils/models/Service"; +import { NextResponse } from "next/server"; + +export async function GET(request: Request) { + const apiEndpoint = `${process.env.NEXT_PUBLIC_API_HOST}/api/service`; + + console.log(apiEndpoint); + + const { searchParams } = new URL(request.url); + const uuid = searchParams.get("uuid"); + + const data = await fetch(`${apiEndpoint}?user_id=${uuid}`); + + const serviceData: Service[] = await data.json(); + // TODO: Remove make every service visible + + const services = serviceData.map((service: Service) => { + service.visible = true; + + return service; + }); + + return NextResponse.json(services, { status: data.status }); +} diff --git a/compass/app/api/service/route.ts b/compass/app/api/service/route.ts deleted file mode 100644 index 771f369..0000000 --- a/compass/app/api/service/route.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { NextResponse } from "next/server"; - -export async function GET() { - return NextResponse.json({ message: "Hello World!" }, { status: 200 }); -} diff --git a/compass/app/resource/page.tsx b/compass/app/resource/page.tsx index fede31d..fc4df14 100644 --- a/compass/app/resource/page.tsx +++ b/compass/app/resource/page.tsx @@ -1,18 +1,18 @@ "use client"; import { PageLayout } from "@/components/PageLayout"; -import { Table } from "@/components/Table/Index"; -import User from "@/utils/models/User"; +import { ResourceTable } from "@/components/Table/ResourceIndex"; +import Resource from "@/utils/models/Resource"; import { createClient } from "@/utils/supabase/client"; -import { UsersIcon } from "@heroicons/react/24/solid"; +import { BookmarkIcon } from "@heroicons/react/24/solid"; import { useEffect, useState } from "react"; export default function Page() { - const [users, setUsers] = useState([]); + const [resources, setResources] = useState([]); useEffect(() => { - async function getUser() { + async function getResources() { const supabase = createClient(); const { data, error } = await supabase.auth.getUser(); @@ -23,22 +23,22 @@ export default function Page() { } const userListData = await fetch( - `${process.env.NEXT_PUBLIC_HOST}/api/user/all?uuid=${data.user.id}` + `${process.env.NEXT_PUBLIC_HOST}/api/resource/all?uuid=${data.user.id}` ); - const users: User[] = await userListData.json(); + const resourcesAPI: Resource[] = await userListData.json(); - setUsers(users); + setResources(resourcesAPI); } - getUser(); + getResources(); }, []); return (
{/* icon + title */} - }> - + }> + ); diff --git a/compass/app/service/layout.tsx b/compass/app/service/layout.tsx index 8d00539..1fff740 100644 --- a/compass/app/service/layout.tsx +++ b/compass/app/service/layout.tsx @@ -27,7 +27,7 @@ export default function RootLayout({ console.log(data, error); if (error) { - console.log("Accessed admin page but not logged in"); + console.log("Accessed service page but not logged in"); router.push("/auth/login"); return; } @@ -38,14 +38,6 @@ export default function RootLayout({ const user: User = await userData.json(); - if (user.role !== Role.ADMIN) { - console.log( - `Accessed admin page but incorrect permissions: ${user.username} ${user.role}` - ); - router.push("/auth/login"); - return; - } - setUser(user); } diff --git a/compass/app/service/page.tsx b/compass/app/service/page.tsx index fede31d..8ebea4f 100644 --- a/compass/app/service/page.tsx +++ b/compass/app/service/page.tsx @@ -1,18 +1,18 @@ "use client"; import { PageLayout } from "@/components/PageLayout"; -import { Table } from "@/components/Table/Index"; -import User from "@/utils/models/User"; +import { ServiceTable } from "@/components/Table/ServiceIndex"; +import Service from "@/utils/models/Service"; import { createClient } from "@/utils/supabase/client"; -import { UsersIcon } from "@heroicons/react/24/solid"; +import { ClipboardIcon } from "@heroicons/react/24/solid"; import { useEffect, useState } from "react"; export default function Page() { - const [users, setUsers] = useState([]); + const [services, setUsers] = useState([]); useEffect(() => { - async function getUser() { + async function getServices() { const supabase = createClient(); const { data, error } = await supabase.auth.getUser(); @@ -22,23 +22,22 @@ export default function Page() { return; } - const userListData = await fetch( - `${process.env.NEXT_PUBLIC_HOST}/api/user/all?uuid=${data.user.id}` + const serviceListData = await fetch( + `${process.env.NEXT_PUBLIC_HOST}/api/service/all?uuid=${data.user.id}` ); - const users: User[] = await userListData.json(); - - setUsers(users); + const servicesAPI: Service[] = await serviceListData.json(); + setUsers(servicesAPI); } - getUser(); + getServices(); }, []); return (
{/* icon + title */} - }> -
+ }> + ); diff --git a/compass/components/Table/ResourceIndex.tsx b/compass/components/Table/ResourceIndex.tsx index 931b039..a714836 100644 --- a/compass/components/Table/ResourceIndex.tsx +++ b/compass/components/Table/ResourceIndex.tsx @@ -32,7 +32,7 @@ import { } from "@heroicons/react/24/solid"; import TagsInput from "../TagsInput/Index"; import { rankItem } from "@tanstack/match-sorter-utils"; -import User from "@/utils/models/User"; +import Resource from "@/utils/models/Resource"; // For search const fuzzyFilter = ( @@ -51,8 +51,9 @@ const fuzzyFilter = ( return itemRank.passed; }; -export const Table = ({ users }: { users: User[] }) => { - const columnHelper = createColumnHelper(); +// TODO: Rename everything to resources +export const ResourceTable = ({ users }: { users: Resource[] }) => { + const columnHelper = createColumnHelper(); useEffect(() => { const sortedUsers = [...users].sort((a, b) => @@ -114,15 +115,15 @@ export const Table = ({ users }: { users: User[] }) => { id: "options", cell: (props) => ( deleteUser(props.row.original.id)} + onDelete={() => {}} onHide={() => hideUser(props.row.original.id)} /> ), }), - columnHelper.accessor("username", { + columnHelper.accessor("name", { header: () => ( <> - Username + Name ), cell: (info) => ( @@ -133,47 +134,44 @@ export const Table = ({ users }: { users: User[] }) => { /> ), }), - columnHelper.accessor("role", { + columnHelper.accessor("link", { header: () => ( <> - {" "} - Role + Link ), cell: (info) => ( - - ), - }), - columnHelper.accessor("email", { - header: () => ( - <> - Email - - ), - cell: (info) => ( - + {info.getValue()} - + ), }), columnHelper.accessor("program", { header: () => ( <> - {" "} - Program + Program ), cell: (info) => , }), + + columnHelper.accessor("summary", { + header: () => ( + <> + Summary + + ), + cell: (info) => ( + {info.getValue()} + ), + }), ]; - const [data, setData] = useState([...users]); + const [data, setData] = useState([...users]); const addUser = () => { setData([...data]); @@ -196,7 +194,7 @@ export const Table = ({ users }: { users: User[] }) => { // TODO: Sorting // added this fn for editing rows - const handleRowUpdate = (updatedRow: User) => { + const handleRowUpdate = (updatedRow: Resource) => { const dataIndex = data.findIndex((row) => row.id === updatedRow.id); if (dataIndex !== -1) { const updatedData = [...data]; diff --git a/compass/components/Table/ServiceIndex.tsx b/compass/components/Table/ServiceIndex.tsx new file mode 100644 index 0000000..6895984 --- /dev/null +++ b/compass/components/Table/ServiceIndex.tsx @@ -0,0 +1,312 @@ +// for showcasing to compass + +import users from "./users.json"; +import { + Cell, + ColumnDef, + Row, + createColumnHelper, + flexRender, + getCoreRowModel, + getFilteredRowModel, + sortingFns, + useReactTable, +} from "@tanstack/react-table"; +import { + ChangeEvent, + useState, + useEffect, + FunctionComponent, + useRef, + ChangeEventHandler, + Key, +} from "react"; +import { RowOptionMenu } from "./RowOptionMenu"; +import { RowOpenAction } from "./RowOpenAction"; +import { TableAction } from "./TableAction"; +import { + AtSymbolIcon, + Bars2Icon, + ArrowDownCircleIcon, + PlusIcon, +} from "@heroicons/react/24/solid"; +import TagsInput from "../TagsInput/Index"; +import { rankItem } from "@tanstack/match-sorter-utils"; +import Service from "@/utils/models/Service"; + +// For search +const fuzzyFilter = ( + row: Row, + columnId: string, + value: any, + addMeta: (meta: any) => void +) => { + // Rank the item + const itemRank = rankItem(row.getValue(columnId), value); + + // Store the ranking info + addMeta(itemRank); + + // Return if the item should be filtered in/out + return itemRank.passed; +}; + +// TODO: Rename everything to service +export const ServiceTable = ({ users }: { users: Service[] }) => { + const columnHelper = createColumnHelper(); + + useEffect(() => { + const sortedUsers = [...users].sort((a, b) => + a.visible === b.visible ? 0 : a.visible ? -1 : 1 + ); + setData(sortedUsers); + }, [users]); + + const deleteUser = (userId: number) => { + console.log(data); + setData((currentData) => + currentData.filter((user) => user.id !== userId) + ); + }; + + const hideUser = (userId: number) => { + console.log(`Toggling visibility for user with ID: ${userId}`); + setData((currentData) => { + const newData = currentData + .map((user) => { + if (user.id === userId) { + return { ...user, visible: !user.visible }; + } + return user; + }) + .sort((a, b) => + a.visible === b.visible ? 0 : a.visible ? -1 : 1 + ); + + console.log(newData); + return newData; + }); + }; + const [presetOptions, setPresetOptions] = useState([ + "administrator", + "volunteer", + "employee", + ]); + const [tagColors, setTagColors] = useState(new Map()); + + const getTagColor = (tag: string) => { + if (!tagColors.has(tag)) { + const colors = [ + "bg-cyan-100", + "bg-blue-100", + "bg-green-100", + "bg-yellow-100", + "bg-purple-100", + ]; + const randomColor = + colors[Math.floor(Math.random() * colors.length)]; + setTagColors(new Map(tagColors).set(tag, randomColor)); + } + return tagColors.get(tag); + }; + + const columns = [ + columnHelper.display({ + id: "options", + cell: (props) => ( + {}} + onHide={() => hideUser(props.row.original.id)} + /> + ), + }), + columnHelper.accessor("name", { + header: () => ( + <> + Name + + ), + cell: (info) => ( + + ), + }), + columnHelper.accessor("status", { + header: () => ( + <> + Status + + ), + cell: (info) => ( + {info.getValue()} + ), + }), + columnHelper.accessor("program", { + header: () => ( + <> + Program + + ), + cell: (info) => , + }), + columnHelper.accessor("requirements", { + header: () => ( + <> + Requirements + + ), + cell: (info) => ( + + ), + }), + + columnHelper.accessor("summary", { + header: () => ( + <> + Summary + + ), + cell: (info) => ( + {info.getValue()} + ), + }), + ]; + + const [data, setData] = useState([...users]); + + const addUser = () => { + setData([...data]); + }; + + // Searching + const [query, setQuery] = useState(""); + const handleSearchChange = (e: ChangeEvent) => { + const target = e.target as HTMLInputElement; + setQuery(String(target.value)); + }; + + const handleCellChange = (e: ChangeEvent, key: Key) => { + const target = e.target as HTMLInputElement; + console.log(key); + }; + + // TODO: Filtering + + // TODO: Sorting + + // added this fn for editing rows + const handleRowUpdate = (updatedRow: Service) => { + const dataIndex = data.findIndex((row) => row.id === updatedRow.id); + if (dataIndex !== -1) { + const updatedData = [...data]; + updatedData[dataIndex] = updatedRow; + setData(updatedData); + } + }; + + const table = useReactTable({ + columns, + data, + filterFns: { + fuzzy: fuzzyFilter, + }, + state: { + globalFilter: query, + }, + onGlobalFilterChange: setQuery, + globalFilterFn: fuzzyFilter, + getCoreRowModel: getCoreRowModel(), + }); + + const handleRowData = (row: any) => { + const rowData: any = {}; + row.cells.forEach((cell: any) => { + rowData[cell.column.id] = cell.value; + }); + // Use rowData object containing data from all columns for the current row + console.log(rowData); + return rowData; + }; + + return ( +
+
+ +
+
+ + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header, i) => ( + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => { + // Individual row + const isUserVisible = row.original.visible; + const rowClassNames = `text-gray-800 border-y lowercase hover:bg-gray-50 ${ + !isUserVisible ? "bg-gray-200 text-gray-500" : "" + }`; + return ( + + {row.getVisibleCells().map((cell, i) => ( + + ))} + + ); + })} + + + + + + +
+ {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} +
+ {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} +
+ + + New + +
+
+ ); +}; diff --git a/compass/components/TagsInput/Index.tsx b/compass/components/TagsInput/Index.tsx index e4252ca..19c2a77 100644 --- a/compass/components/TagsInput/Index.tsx +++ b/compass/components/TagsInput/Index.tsx @@ -6,6 +6,9 @@ import { CreateNewTagAction } from "./CreateNewTagAction"; interface TagsInputProps { presetOptions: string[]; + presetValue: string | string[]; + setPresetOptions: () => {}; + getTagColor: () => {}; } const TagsInput: React.FC = ({ @@ -34,8 +37,12 @@ const TagsInput: React.FC = ({ } }; - const handleOutsideClick = (event) => { - if (dropdown.current && !dropdown.current.contains(event.target)) { + // TODO: Fix MouseEvent type and remove the as Node as that is completely wrong + const handleOutsideClick = (event: MouseEvent) => { + if ( + dropdown.current && + !dropdown.current.contains(event.target as Node) + ) { setCellSelected(false); // Remove event listener after handling outside click window.removeEventListener("click", handleOutsideClick); @@ -117,7 +124,11 @@ const TagsInput: React.FC = ({ return (
{!cellSelected ? ( - + ) : (
diff --git a/compass/components/TagsInput/TagsArray.tsx b/compass/components/TagsInput/TagsArray.tsx index 973ddf8..c014e7c 100644 --- a/compass/components/TagsInput/TagsArray.tsx +++ b/compass/components/TagsInput/TagsArray.tsx @@ -1,8 +1,8 @@ import { Tag } from "./Tag"; export interface Tags { - tags: string[]; - handleDelete: () => {}; + tags: Set; + handleDelete: (tag: string) => void; active: boolean; } diff --git a/compass/package-lock.json b/compass/package-lock.json index 9142869..dad8edd 100644 --- a/compass/package-lock.json +++ b/compass/package-lock.json @@ -13,9 +13,12 @@ "@supabase/supabase-js": "^2.42.3", "@tanstack/match-sorter-utils": "^8.15.1", "@tanstack/react-table": "^8.15.0", + "bufferutil": "^4.0.8", "next": "13.5.6", "react": "^18", - "react-dom": "^18" + "react-dom": "^18", + "utf-8-validate": "^6.0.3", + "ws": "^8.16.0" }, "devDependencies": { "@types/node": "^20", @@ -572,26 +575,6 @@ "ws": "^8.14.2" } }, - "node_modules/@supabase/realtime-js/node_modules/ws": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", - "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/@supabase/ssr": { "version": "0.3.0", "license": "MIT", @@ -2973,6 +2956,18 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/bufferutil": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz", + "integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==", + "hasInstallScript": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -7236,6 +7231,16 @@ "node": ">=0.10.0" } }, + "node_modules/node-gyp-build": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz", + "integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -8229,6 +8234,18 @@ "node": ">=14.17" } }, + "node_modules/utf-8-validate": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-6.0.3.tgz", + "integrity": "sha512-uIuGf9TWQ/y+0Lp+KGZCMuJWc3N9BHA+l/UmHd/oUHwJJDeysyTRxNQVkbzsIWfGFbRe3OcgML/i0mvVRPOyDA==", + "hasInstallScript": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", @@ -8279,6 +8296,26 @@ "node": ">=8" } }, + "node_modules/ws": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/compass/package.json b/compass/package.json index 9d9a681..bd2533c 100644 --- a/compass/package.json +++ b/compass/package.json @@ -15,9 +15,12 @@ "@supabase/supabase-js": "^2.42.3", "@tanstack/match-sorter-utils": "^8.15.1", "@tanstack/react-table": "^8.15.0", + "bufferutil": "^4.0.8", "next": "13.5.6", "react": "^18", - "react-dom": "^18" + "react-dom": "^18", + "utf-8-validate": "^6.0.3", + "ws": "^8.16.0" }, "devDependencies": { "@types/node": "^20", diff --git a/compass/utils/models/Resource.ts b/compass/utils/models/Resource.ts new file mode 100644 index 0000000..c6c029c --- /dev/null +++ b/compass/utils/models/Resource.ts @@ -0,0 +1,11 @@ +import { Program } from "./User"; + +export default interface Resource { + id: number; + created_at: Date; + name: string; + summary: string; + link: string; + program: Program; + visible: boolean; +} diff --git a/compass/utils/models/Service.ts b/compass/utils/models/Service.ts new file mode 100644 index 0000000..5093c5f --- /dev/null +++ b/compass/utils/models/Service.ts @@ -0,0 +1,12 @@ +import { Program } from "./User"; + +export default interface Service { + id: number; + created_at: Date; + name: string; + status: string; + summary: string; + requirements: string[]; + program: Program; + visible: boolean; +}