diff --git a/.github/workflows/supabase-pull-request.yml b/.github/workflows/supabase-pull-request.yml deleted file mode 100644 index 58c3351..0000000 --- a/.github/workflows/supabase-pull-request.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Deploy to Supabase on PR - -on: pull_request - -jobs: - deploy: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - - name: Set up Node.js - uses: actions/setup-node@v3 - with: - node-version: '18' # Ensure compatibility with Supabase CLI - - - name: Install dependencies - run: npm install - - - name: Install Supabase CLI locally - run: npm install supabase - - - name: Authenticate Supabase CLI - run: npx supabase login --token "${{ secrets.SUPABASE_ACCESS_TOKEN }}" - - - name: Link Supabase Project - run: npx supabase link --project-ref "${{ secrets.SUPABASE_PROJECT_REF }}" --password "${{ secrets.SUPABASE_DB_PWD }}" - - - name: Deploy migrations - run: npx supabase db push --password "${{ secrets.SUPABASE_DB_PWD }}" --include-all - - - name: Deploy Edge Functions - run: | - if [ -d "supabase/functions" ] && [ "$(ls -A supabase/functions)" ]; then - npx supabase functions deploy - else - echo "No functions to deploy." - fi diff --git a/src/api/cePlanService.ts b/src/api/cePlanService.ts index 9601662..21a6a76 100644 --- a/src/api/cePlanService.ts +++ b/src/api/cePlanService.ts @@ -19,7 +19,7 @@ const TEST_PLAN = { cohort_id: '1', student_id: 's1', student: { - id: '', + id: 's1', name: 'Student 1', age: null, email: '', @@ -29,6 +29,7 @@ const TEST_PLAN = { availabilities: [] }, anchor: true, + priority: true, availabilities: [] } , { @@ -36,7 +37,7 @@ const TEST_PLAN = { cohort_id: '1', student_id: 's2', student: { - id: '', + id: 's2', name: 'Student 2', age: null, email: '', @@ -46,6 +47,7 @@ const TEST_PLAN = { availabilities: [] }, anchor: false, + priority: true, availabilities: [] }, { @@ -53,7 +55,7 @@ const TEST_PLAN = { cohort_id: '1', student_id: 's3', student: { - id: '', + id: 's3', name: 'Student 3', age: null, email: '', @@ -63,6 +65,7 @@ const TEST_PLAN = { availabilities: [] }, anchor: false, + priority: false, availabilities: [] } ] as Placement[], @@ -86,8 +89,7 @@ const TEST_PLAN = { } as Plan; class CEPlanService { - async getById(id: string): Promise { - console.log("get plan", id); + async getById(_id: string): Promise { return TEST_PLAN; } diff --git a/src/api/types.ts b/src/api/types.ts index 7dfcd6a..acf2d25 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -63,6 +63,7 @@ type Placement = Entity & { student_id: string; student: Student; anchor: boolean; + priority: boolean; availabilities: Availability[]; } diff --git a/src/components/PlanDetails/GroupBoard.tsx b/src/components/PlanDetails/GroupBoard.tsx index 1b834c1..22f43bd 100644 --- a/src/components/PlanDetails/GroupBoard.tsx +++ b/src/components/PlanDetails/GroupBoard.tsx @@ -8,71 +8,80 @@ import { ReactNode, useEffect, useState } from "react"; -import { StarFilled } from "@ant-design/icons"; +import { ExclamationCircleFilled, StarFilled } from "@ant-design/icons"; +import { DDCategory, DDType, DragAndDrop } from '@digitalaidseattle/draganddrop'; import { Box, - Button, Card, CardContent, + Stack, Typography } from "@mui/material"; -import { DragAndDrop, DDCategory, DDType } from '@digitalaidseattle/draganddrop'; -import { Placement, Plan } from "../../api/types"; +import { Placement } from "../../api/types"; +import { PlanProps } from "../../utils/props"; export const StudentCard = (props: { placement: Placement }) => { - + const anchor = props.placement.anchor ? 'green' : 'gray;' + const priority = props.placement.priority ? 'green' : 'gray;' return ( - {props.placement.anchor && } {props.placement.student.name} + + + {props.placement.anchor && + + } + {props.placement.priority && + + } + + {props.placement.student.name} + ); } -type EnrollmentWrapper = Placement & DDType +type PlacementWrapper = Placement & DDType -export const GroupBoard = (props: { plan: Plan | undefined }) => { +export const GroupBoard: React.FC = ({ plan }) => { const [categories, setCategories] = useState[]>(); useEffect(() => { - if (props.plan) { - setCategories(props.plan.groups.map(group => { - return { label: group.groupNo, value: group.groupNo } - })) - } - }, [props]) + setCategories(plan.groups.map(group => { + return { label: group.groupNo, value: group.groupNo } + })) + }, [plan]) function handleChange(c: Map, t: Placement) { console.log(c, t) } - function isCategory(item: EnrollmentWrapper, category: DDCategory): boolean { - if (props.plan) { - const group = props.plan.groups.find(group => group.groupNo === category.value); + function isCategory(item: PlacementWrapper, category: DDCategory): boolean { + if (plan) { + const group = plan.groups.find(group => group.groupNo === category.value); return group ? group.studentIds.includes(item.student_id) : false; } return false; } - function cellRender(item: EnrollmentWrapper): ReactNode { + function cellRender(item: PlacementWrapper): ReactNode { return } return ( <> - - <>{props.plan && categories && + <>{plan && categories && , e: Placement) => handleChange(c, e)} - items={props.plan.placements} + items={plan.placements} categories={categories} isCategory={isCategory} cardRenderer={cellRender} />} - {!props.plan && + {!plan && No plan found. } diff --git a/src/components/PlanDetails/GroupCount.tsx b/src/components/PlanDetails/GroupCount.tsx new file mode 100644 index 0000000..00ea2e1 --- /dev/null +++ b/src/components/PlanDetails/GroupCount.tsx @@ -0,0 +1,63 @@ + +/** + * GroupCount.tsx + * + * @copyright 2024 Digital Aid Seattle + * + */ + +import { useEffect, useState } from "react"; + +import { + Box, + Card, + CardContent, + Slider, + Stack, + Typography +} from "@mui/material"; +import { PlanProps } from "../../utils/props"; + +export const GroupCount: React.FC = ({ plan }) => { + const [values, setValues] = useState([0, 10]); + + const [min, setMin] = useState(5); + const [max, setMax] = useState(10); + + useEffect(() => { + // TODO base values, min, & max on student counts + setValues([5, 10]) + setMin(5); + setMax(10); + }, [plan]) + + function handleChange(_event: Event, newValue: number | number[]): void { + console.log(newValue) + setValues(newValue as number[]); + } + + return ( + <> + + + + + Number of Groups + + + + + + + + + ) +}; diff --git a/src/components/PlanDetails/SetupStudents.tsx b/src/components/PlanDetails/SetupStudents.tsx new file mode 100644 index 0000000..b1b237c --- /dev/null +++ b/src/components/PlanDetails/SetupStudents.tsx @@ -0,0 +1,208 @@ +/** + * SetupPanel.tsx + * + * Example of integrating tickets with data-grid + */ +import { useEffect, useState } from 'react'; + +// material-ui +import { + Box, + Button, + Stack +} from '@mui/material'; +import { + DataGrid, + GridColDef, + GridRenderCellParams, + GridRowId, + GridRowSelectionModel, + GridSortModel, + useGridApiRef +} from '@mui/x-data-grid'; + +// third-party + +// project import +import { ExclamationCircleFilled, StarFilled } from '@ant-design/icons'; +import { PageInfo } from '@digitalaidseattle/supabase'; +import { PlanProps } from '../../utils/props'; +import { Student } from '../../api/types'; + +const PAGE_SIZE = 10; + +type EnrolledStudent = Student & { enrollmentId: string, anchor: boolean, priority: boolean } + +export const SetupStudents: React.FC = ({ plan }) => { + + const apiRef = useGridApiRef(); + + const [paginationModel, setPaginationModel] = useState({ page: 0, pageSize: PAGE_SIZE }); + const [sortModel, setSortModel] = useState([{ field: 'created_at', sort: 'desc' }]) + const [rowSelectionModel, setRowSelectionModel] = useState(); + const [pageInfo, setPageInfo] = useState>({ rows: [], totalRowCount: 0 }); + + useEffect(() => { + const enrolledStudents = plan.placements.map(placement => { + return { + ...placement.student, + enrollmentId: placement.id, + anchor: placement.anchor, + priority: placement.priority, + } as EnrolledStudent + }); + setPageInfo({ + rows: enrolledStudents, + totalRowCount: enrolledStudents.length + }) + }, [plan]) + + const applyAnchor = () => { + rowSelectionModel?.forEach((n: GridRowId) => { + const row = pageInfo.rows.find(r => r.id === n) + if (row) { + row.anchor = true; + } + }) + setPageInfo({ ...pageInfo }); + } + + const applyPriority = () => { + rowSelectionModel?.forEach((n: GridRowId) => { + const row = pageInfo.rows.find(r => r.id === n) + if (row) { + row.priority = true; + } + }) + setPageInfo({ ...pageInfo }); + } + + const addStudent = () => { + alert(`Add student not implemented yet`) + } + + const removeStudent = () => { + alert(`Remove student not implemented yet`) + } + + const toggleAnchor = (student: EnrolledStudent) => { + student.anchor = !student.anchor; + setPageInfo({ ...pageInfo }); + } + + const togglePriority = (student: EnrolledStudent) => { + student.priority = !student.priority + setPageInfo({ ...pageInfo }); + } + + const getColumns = (): GridColDef[] => { + return [ + { + field: 'anchor', + headerName: 'Anchor', + width: 100, + type: 'boolean', + renderCell: (param: GridRenderCellParams) => { + return toggleAnchor(param.row)} /> + } + }, + { + field: 'priority', + headerName: 'Priority', + width: 100, + type: 'boolean', + renderCell: (param: GridRenderCellParams) => { + return togglePriority(param.row)} /> + } + }, + { + field: 'name', + headerName: 'Name', + width: 150, + }, + { + field: 'email', + headerName: 'Email', + width: 140, + }, + { + field: 'city', + headerName: 'City', + width: 140, + }, + { + field: 'country', + headerName: 'Country', + width: 140, + }, + { + field: 'availability', + headerName: 'Availability', + width: 140, + } + ]; + } + + return ( + + + + + + + + {plan && + + } + + ); +} diff --git a/src/components/PlanDetails/index.tsx b/src/components/PlanDetails/index.tsx index 959c81c..c280903 100644 --- a/src/components/PlanDetails/index.tsx +++ b/src/components/PlanDetails/index.tsx @@ -6,41 +6,112 @@ */ import { MainCard } from '@digitalaidseattle/mui'; -import { Box, Tab, Tabs } from '@mui/material'; +import { Box, Button, Stack, Step, StepLabel, Stepper } from '@mui/material'; import { useState } from "react"; -import { TabPanel } from "../TabPanel"; +import { PlanProps } from '../../utils/props'; import { TextEdit } from "../TextEdit"; import { GroupBoard } from "./GroupBoard"; -import Setup from "./Setup"; -import { Plan } from '../../api/types'; +import { GroupCount } from './GroupCount'; +import { SetupStudents } from './SetupStudents'; -export const PlanDetails = (props: { plan: Plan }) => { - const [value, setValue] = useState(0); - const plan = props.plan; +// const TabbedDetails: React.FC = ({ plan }) => { +// const [value, setValue] = useState(0); - const handleChange = (_event: React.SyntheticEvent, newValue: number) => { - setValue(newValue); +// const changeTab = (_event: React.SyntheticEvent, newValue: number) => { +// setValue(newValue); +// }; + +// return ( +// <> +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// ); +// } + +const SteppedDetails: React.FC = ({ plan }) => { + const steps = ['Setup Students', 'Number of Groups', 'Review']; + + const [activeStep, setActiveStep] = useState(0); + + const handleBack = () => { + setActiveStep((prevActiveStep) => prevActiveStep - 1); }; + const handleNext = () => { + setActiveStep((prevActiveStep) => prevActiveStep + 1); + }; + + return ( + <> + + + {steps.map((label) => { + const stepProps: { completed?: boolean } = {}; + const labelProps: { + optional?: React.ReactNode; + } = {}; + + return ( + + {label} + + ); + })} + + <> + + + + + + {activeStep === 0 && } + {activeStep === 1 && } + {activeStep === 2 && } + + + ); +} + + +export const PlanDetails: React.FC = ({ plan }) => { + // TODO add breadcrumbs return ( - - alert(`TODO save : ${text} name`)} /> - alert(`TODO note save : ${text}`)} /> - - - - - - - - - - - - - + + + alert(`TODO save : ${text} name`)} /> + alert(`TODO note save : ${text}`)} /> + + + {/* */} + + ); } diff --git a/src/utils/props.ts b/src/utils/props.ts new file mode 100644 index 0000000..a43576a --- /dev/null +++ b/src/utils/props.ts @@ -0,0 +1,13 @@ +/** + * props.ts + * + * @copyright 2025 Digital Aid Seattle + * + */ + +import { Plan } from "../api/types"; + +export interface PlanProps { + plan: Plan; + onChange?: (plan: Plan) => void; +} \ No newline at end of file