diff --git a/src/pages/testing/index.tsx b/src/pages/testing/index.tsx
index 1dc3c1e..d2952fd 100644
--- a/src/pages/testing/index.tsx
+++ b/src/pages/testing/index.tsx
@@ -1,6 +1,11 @@
// use this to test your stuff
+import QuickShiftBtn from '@/sprintFiles/QuickShift/QuickShiftBtn'
const index = () => {
- return
inex
+ return (
+
+
+
+ )
}
export default index
diff --git a/src/sprintFiles/QuickShift/NewQuickShiftCard.tsx b/src/sprintFiles/QuickShift/NewQuickShiftCard.tsx
new file mode 100644
index 0000000..fbaa4f9
--- /dev/null
+++ b/src/sprintFiles/QuickShift/NewQuickShiftCard.tsx
@@ -0,0 +1,57 @@
+import {
+ Button,
+ Dialog,
+ Typography,
+ DialogContent,
+ DialogTitle,
+} from '@mui/material'
+import { useState } from 'react'
+import ScheduledShiftForm from './QuickShiftForm'
+
+//Quick Shift == New Shift card that deals wi
+//TODOS: Look below
+// Split quickshift card into shift card and quickshiftbtn.
+// Have an assigned user. Well-defined date.
+// no categories
+// select a single day
+// skips the shift step of creating schedule.
+// will only create a scheduled shift, NOT a SHIFT object.
+function NewQuickShiftCard({
+ setOpen,
+ open,
+}: {
+ setOpen: (value: React.SetStateAction) => void
+ open: boolean
+}) {
+ // const [shiftValues, setShiftValues] = useState(null)
+ const handleClose = () => {
+ setOpen(false)
+ }
+
+ const handleOpen = () => {
+ setOpen(true)
+ }
+ return (
+ <>
+
+ >
+ )
+}
+export default NewQuickShiftCard
diff --git a/src/sprintFiles/QuickShift/QuickShiftBtn.tsx b/src/sprintFiles/QuickShift/QuickShiftBtn.tsx
new file mode 100644
index 0000000..bc51f5f
--- /dev/null
+++ b/src/sprintFiles/QuickShift/QuickShiftBtn.tsx
@@ -0,0 +1,39 @@
+import {
+ Button,
+ Dialog,
+ Typography,
+ DialogContent,
+ DialogTitle,
+} from '@mui/material'
+import React from 'react'
+import { useState } from 'react'
+import NewQuickShiftCard from './NewQuickShiftCard'
+
+//Quick Shift == New Shift card that deals wi
+//TODOS: Look below
+// Split quickshift card into shift card and quickshiftbtn.
+// Have an assigned user. Well-defined date.
+// no categories
+// select a single day
+// skips the shift step of creating schedule.
+// will only create a scheduled shift, NOT a SHIFT object.
+function QuickShiftBtn() {
+ const [open, setOpen] = useState(false)
+ // const [shiftValues, setShiftValues] = useState(null)
+ const handleClose = () => {
+ setOpen(false)
+ }
+
+ const handleOpen = () => {
+ setOpen(true)
+ }
+ return (
+
+
+
+
+ )
+}
+export default QuickShiftBtn
diff --git a/src/sprintFiles/QuickShift/QuickShiftForm.tsx b/src/sprintFiles/QuickShift/QuickShiftForm.tsx
new file mode 100644
index 0000000..2aa1dfa
--- /dev/null
+++ b/src/sprintFiles/QuickShift/QuickShiftForm.tsx
@@ -0,0 +1,406 @@
+import { Formik, Form, FormikHelpers, Field } from 'formik'
+import {
+ Stack,
+ Button,
+ Typography,
+ TextField,
+ Autocomplete,
+} from '@mui/material'
+import { DatePicker, LocalizationProvider } from '@mui/x-date-pickers'
+import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'
+import { MobileTimePicker } from '@mui/x-date-pickers/MobileTimePicker'
+import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker'
+
+import dayjs, { Dayjs } from 'dayjs'
+import * as Yup from 'yup'
+import {
+ TextInput,
+ SelectInput,
+} from '../../components/shared/forms/CustomFormikFields'
+import { useSelector } from 'react-redux'
+import React, { useEffect, useState } from 'react'
+import { RootState } from '../../store/store'
+import { EntityId } from '@reduxjs/toolkit'
+import { House, ScheduledShift, Shift, User } from '../../types/schema'
+import styles from './ShiftForm.module.css'
+import {
+ selectScheduledShiftById,
+ useAddNewScheduledShiftMutation,
+ useUpdateScheduledShiftMutation,
+} from './scheduledShiftApiSlice'
+import {
+ selectShiftById,
+ useAddNewShiftMutation,
+ useUpdateShiftMutation,
+} from '@/features/shift/shiftApiSlice'
+import { useGetShiftsQuery } from '@/features/shift/shiftApiSlice'
+import { selectCurrentHouse } from '@/features/auth/authSlice'
+import { useGetUsersQuery } from '@/features/user/userApiSlice'
+import uuid from 'react-uuid'
+import { formatMilitaryTime } from '@/utils/utils'
+
+const ShiftSchema = Yup.object({
+ name: Yup.string()
+ .required('Name is required')
+ .min(1, 'Name must have at least 1 characters'),
+ description: Yup.string(),
+ possibleDays: Yup.array().of(Yup.string()),
+ startTime: Yup.date().required('Start time is required'),
+ endTime: Yup.date().required('End time is required'),
+ // category: Yup.string().required('Cagegory is required'),
+ hours: Yup.number().required('Hours credit is required'),
+ verificationBuffer: Yup.number(),
+ assignedDay: Yup.string(),
+
+ assignedUser: Yup.object(), //reassign the assignedUser
+ date: Yup.date(),
+})
+
+const daysList = [
+ 'Monday',
+ 'Tuesday',
+ 'Wednesday',
+ 'Thursday',
+ 'Friday',
+ 'Saturday',
+ 'Sunday',
+]
+
+// const shiftCategories = ['cook dinner', 'clean bathroom', 'wash dishes', 'clean basement']
+
+const emptyShift = {
+ name: '',
+ category: '',
+ possibleDays: [],
+ startTime: dayjs('2023-04-17T12:00'),
+ endTime: dayjs('2023-04-17T18:30'),
+ hours: 0,
+ despription: '',
+ verificationBuffer: 0,
+ assignedUser: { label: '', id: '' },
+ assignedDay: dayjs(),
+}
+
+/**
+ * 1. Fill out the quick shift form like a shift form
+ * 2. Formik uses validation schema + setField value to track form changes
+ * 3. For any option, setFieldValue, TextField, and other formik components/funcs will update initialValues
+ * 4. When submit is pressed, initialValues is sent to onSubmit function.
+ * 5. onSubmit function accesses the field values from formik. Can manipulate as wanted
+ * 6. OnSubmit formats the field info to match a scheduled shift. Sends it to firebase
+ */
+
+const QuickShiftForm = ({
+ setOpen,
+ shiftId,
+ isNewShift,
+}: {
+ setOpen: (value: React.SetStateAction) => void
+ shiftId?: string
+ isNewShift: boolean
+}) => {
+ // const authUser = useSelector(selectCurrentUser) as User
+ const currentHouse = useSelector(selectCurrentHouse) as House
+
+ //** House shifts */
+ const { data: shiftsData, isSuccess: isShiftsSuccess } = useGetShiftsQuery(
+ currentHouse.id
+ )
+
+ //** for editing shifts */
+ const shift: Shift = useSelector(
+ (state: RootState) =>
+ selectShiftById(currentHouse.id)(state, shiftId as EntityId) as Shift
+ )
+
+ //** Holds the house shifts categories */
+ const [houseCategories, setHouseCategories] = useState([
+ 'Uncategorized',
+ ])
+
+ //* Get API helpers to create or update a shift
+ const [
+ addNewScheduledShift,
+ {
+ // isLoading: isLoadingNewShift,
+ // isSuccess: isSuccessNewShift,
+ // isError: isErrorNewShift,
+ // error: errorNewShift,
+ },
+ ] = useAddNewScheduledShiftMutation()
+ const [
+ updateScheduledShift,
+ {
+ // isLoading: isLoadingUpdateShift,
+ // isSuccess: isSuccessUpdateShift,
+ // isError: isErrorUpdateShift,
+ // error: errorUpdateShift,
+ },
+ ] = useUpdateScheduledShiftMutation()
+
+ const [chosenDate, setDate] = useState(null)
+
+ /**
+ *
+ * Has part of the data formatted into JSONcopy, which is what the original shift should've looked like
+ * Note that JSONCopy won't match a Shift object in our firebase like a regular scheduled shift would
+ * Rest of info is stored in a defaulted scheduledShift. Bare min. info to fit a quickshift.
+ *
+ */
+ const onSubmit = async (
+ values: {
+ name: string
+ category: string
+ hours: number
+ startTime: Dayjs
+ endTime: Dayjs
+ possibleDays: string[]
+ description: string
+ verificationBuffer: number
+
+ assignedDay: Dayjs
+ assignedUser: labeledUser | undefined
+ },
+ formikBag: FormikHelpers
+ ) => {
+ const {
+ name,
+ category: categoryString,
+ hours,
+ description,
+ possibleDays,
+ startTime: startTimeObject,
+ endTime: endTimeObject,
+ verificationBuffer,
+ assignedUser,
+ assignedDay,
+ } = values
+
+ const startTime = Number(startTimeObject.format('HHmm'))
+ const endTime = Number(endTimeObject.format('HHmm'))
+ let category
+ if (categoryString === undefined || categoryString === 'Uncategorized') {
+ category = ''
+ } else {
+ category = categoryString
+ }
+
+ let result
+ const timeWindow = { startTime, endTime }
+ const timeWindowDisplay =
+ formatMilitaryTime(startTime) + ' - ' + formatMilitaryTime(endTime)
+ const data = { data: {}, houseId: '', shiftId: '' }
+ const shiftObject = {
+ name,
+ category,
+ hours,
+ possibleDays,
+ description,
+ timeWindow,
+ verificationBuffer,
+ timeWindowDisplay,
+ assignedUser,
+ assignedDay,
+ }
+
+ data.data = {
+ id: '',
+ shiftID: '',
+ date: assignedDay.toString(),
+ assignedUser: assignedUser,
+ status: 'live',
+ verifiedBy: '',
+ verifiedAt: '',
+ unverifiedAt: '',
+ penaltyHours: 0,
+ jsonCopy: JSON.stringify(shiftObject), //TODO : check if this fails
+ }
+
+ data.houseId = currentHouse.id
+ data.shiftId = shiftId ? shiftId : ''
+ // console.log('data: ', data)
+ console.log({
+ pushedData: data,
+ datadata: data.data,
+ isNewShift: isNewShift,
+ shiftId: shiftId,
+ })
+
+ if (isNewShift || !shiftId) {
+ result = await addNewScheduledShift(data)
+ } else {
+ result = await updateScheduledShift(data)
+ }
+ if (result) {
+ console.log('success with shift: ', result)
+ }
+
+ formikBag.resetForm()
+ setOpen(false)
+ }
+
+ // React.useEffect(() => {
+ // console.log('This is the selected shift', shift)
+ // }, [shift])
+
+ const {
+ data: users,
+ // isLoading: isUsersLoading,
+ // isSuccess: isUsersSuccess,
+ // isError: isUsersError,
+ } = useGetUsersQuery({})
+
+ //Using this instead of setFieldValue in formik because of issues with the fields
+ //Will use the specific date that a quick shift must be in.
+ const [userOptions, setUserOptions] = useState([{ label: '', id: '' }])
+ type labeledUser = {
+ label: string
+ id: string
+ }
+ const [targetUser, setTargetUser] = useState(userOptions[0])
+
+ useEffect(() => {
+ // console.log({ ents: users?.entities, ids: users?.ids, targuser: targetUser, userOpt: Val: inputValue})
+ {
+ const tempOptions: labeledUser[] = []
+ if (users == undefined) return
+ users.ids?.map((id: EntityId) => {
+ let user = users?.entities[id]
+ if (user != undefined) {
+ let userWithLabel: labeledUser = {
+ label: user.displayName,
+ id: user.id,
+ } //TODO: add an ID here as well.
+ // Object.assign(userWithLabel, user)
+ tempOptions.push(userWithLabel)
+ }
+ })
+ emptyShift.assignedUser = tempOptions[0]
+ setUserOptions(tempOptions)
+ setTargetUser(tempOptions[0])
+ }
+ console.log('OPTIONS: ', { options: userOptions })
+ }, [users])
+ const [inputValue, setInputValue] = React.useState('')
+ /**
+ * 1. load in the options, push the labeled options to a user options list
+ * 2. use the userOptions to create the members list
+ * 3. treat the form like a normal shift form. Except: Categories, assignedUser, possible days will be set to defaults.
+ * 4. when picking an option from the dropdown, change the member OBJECT to match
+ * 5.
+ */
+
+ return (
+ <>
+
+ {({ isSubmitting, values, setFieldValue, errors }) => {
+ return (
+
+ )
+ }}
+
+ >
+ )
+}
+
+export default QuickShiftForm
diff --git a/src/sprintFiles/QuickShift/ShiftForm.module.css b/src/sprintFiles/QuickShift/ShiftForm.module.css
new file mode 100644
index 0000000..b5924ea
--- /dev/null
+++ b/src/sprintFiles/QuickShift/ShiftForm.module.css
@@ -0,0 +1,46 @@
+.shiftBox {
+ padding: 5%;
+}
+
+.title {
+ padding-bottom: 2%;
+ float: left;
+ display: block;
+}
+
+.close {
+ float: right;
+ padding-bottom: 5%;
+ display: block;
+}
+
+.line {
+ width: 95%;
+}
+
+.content {
+}
+
+.formField {
+ width: 100%;
+ padding-bottom: 1%;
+ padding-right: 3%;
+ float: left;
+}
+
+.flex {
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+}
+
+.submit {
+ float: right;
+ margin-bottom: 10px;
+ margin-right: 5%;
+}
+
+.clear {
+ float: left;
+ margin-bottom: 10px;
+}
diff --git a/src/sprintFiles/QuickShift/scheduledShiftApiSlice.ts b/src/sprintFiles/QuickShift/scheduledShiftApiSlice.ts
new file mode 100644
index 0000000..7095a9e
--- /dev/null
+++ b/src/sprintFiles/QuickShift/scheduledShiftApiSlice.ts
@@ -0,0 +1,132 @@
+import { createSelector, createEntityAdapter, EntityId } from '@reduxjs/toolkit'
+import { fbScheduledShift, ScheduledShift } from '../../types/schema'
+import { apiSlice } from '../../store/api/apiSlice'
+import { RootState } from '../../store/store'
+import dayjs from 'dayjs'
+
+const scheduledShiftsAdapter = createEntityAdapter({})
+
+const initialState = scheduledShiftsAdapter.getInitialState()
+
+export const scheduledShiftsApiSlice = apiSlice.injectEndpoints({
+ endpoints: (builder) => ({
+ getScheduledShifts: builder.query({
+ query: (houseId) => ({
+ url: `houses/${houseId}/scheduledShifts`,
+ method: 'GET',
+ data: { body: 'hello world' },
+ params: { queryType: 'scheduledshifts' },
+ // validateStatus: (response, result) => {
+ // console.log('response: ', response, ' -- result: ', result)
+ // return response.status === 200 && !result.isError
+ // },
+ }),
+ // keepUnusedDataFor: 60,
+ //Andrei Note: deal with the conversion of fbSched shift to a JS scheduled shift.
+ transformResponse: (responseData: fbScheduledShift[]) => {
+ const loadedShifts = responseData.map((entity) => {
+ let jsSchedShift: ScheduledShift = {
+ id: entity.id,
+ shiftID: entity.shiftID,
+ date: dayjs(entity.date),
+ assignedUser: entity.assignedUser,
+ status: entity.status,
+ verifiedBy: dayjs(entity.verifiedBy),
+ verifiedAt: dayjs(entity.verifiedAt),
+ unverifiedAt: dayjs(entity.unverifiedAt),
+ penaltyHours: entity.penaltyHours,
+ jsonCopy: entity.jsonCopy,
+ }
+ return jsSchedShift
+ })
+ console.debug(loadedShifts)
+ return scheduledShiftsAdapter.setAll(initialState, loadedShifts)
+ },
+ providesTags: (result) => {
+ if (result?.ids) {
+ return [
+ { type: 'ScheduledShift', id: 'LIST' },
+ ...result.ids.map((id) => ({
+ type: 'ScheduledShift' as const,
+ id,
+ })),
+ ]
+ } else return [{ type: 'ScheduledShift', id: 'LIST' }]
+ },
+ }),
+ addNewScheduledShift: builder.mutation({
+ query: (data) => {
+ console.log({ apiData: data })
+ console.log({ data: data, datadata: data.data })
+
+ return {
+ url: `houses/${data.houseId}/scheduledShifts`,
+ method: 'POST',
+ body: {
+ ...data.data,
+ },
+ }
+ },
+ invalidatesTags: [{ type: 'ScheduledShift', id: 'LIST' }],
+ }),
+ updateScheduledShift: builder.mutation({
+ // query: (data) => ({
+ // url: `houses/${data.houseId}/scheduledShifts/${data.shiftId}`,
+ // method: 'PATCH',
+ // body: {
+ // ...data.data,
+ // },
+ // }),
+ query: (data) => {
+ console.log({ apiDataUpdate: data })
+ console.log({ data: data, datadata: data.data })
+
+ return {
+ url: `houses/${data.houseId}/scheduledShifts/${data.shiftId}`,
+ method: 'PATCH',
+ body: {
+ ...data.data,
+ },
+ }
+ },
+
+ invalidatesTags: (result, error, arg) => [
+ { type: 'ScheduledShift', id: arg.id },
+ ],
+ }),
+ // deleteScheduledShifts: builder.mutation({
+ // query: (data) => ({
+ // url: `houses/${data.houseId}/scheduledShifts/${data.shiftId}`,
+ // method: 'DELETE',
+ // }),
+ // invalidatesTags: (result, error, arg) => [{ type: 'Shift', id: arg.id }],
+ // }),
+ }),
+})
+
+export const {
+ useGetScheduledShiftsQuery,
+ useAddNewScheduledShiftMutation,
+ useUpdateScheduledShiftMutation,
+ // useDeleteScheduledShiftsMutation,
+} = scheduledShiftsApiSlice
+
+// Creates memoized selector to get normalized state based on the query parameter
+const selectScheduledShiftsData = createSelector(
+ (state: RootState, queryParameter: string) =>
+ scheduledShiftsApiSlice.endpoints.getScheduledShifts.select(queryParameter)(
+ state
+ ),
+ (scheduledShiftsResult) => scheduledShiftsResult.data ?? initialState
+)
+
+// Creates memoized selector to get a shift by its ID based on the query parameter
+export const selectScheduledShiftById = (queryParameter: string) =>
+ createSelector(
+ (state: RootState) => selectScheduledShiftsData(state, queryParameter),
+ (_: unknown, scheduledShiftId: EntityId) => scheduledShiftId,
+ (
+ data: { entities: { [x: string]: unknown } },
+ scheduledShiftId: string | number
+ ) => data.entities[scheduledShiftId]
+ )
diff --git a/src/types/schema.ts b/src/types/schema.ts
index 7fc1681..a25ab9a 100644
--- a/src/types/schema.ts
+++ b/src/types/schema.ts
@@ -1,3 +1,5 @@
+import dayjs from 'dayjs'
+
export type User = {
// this id is to help the standard table generalize the id attribute
id?: string
@@ -80,7 +82,23 @@ export type Shift = {
}
// TODO: add date, verifiedAt, and unverifiedBy attributes
+//TODO : When getting date, verifiedBy, and verifiedAt, unverifiedAt : turn from string->date obj, when setting : turn from date obj -> string.
+// Todo: Get: pull the string, then call JSON.parse on it (cast). Set: turn it into a string.
export type ScheduledShift = {
+ id: string
+ shiftID: string
+ date: dayjs.Dayjs
+ assignedUser: string
+ status: string
+ verifiedBy: dayjs.Dayjs
+ verifiedAt: dayjs.Dayjs
+ unverifiedAt: dayjs.Dayjs
+ penaltyHours: number
+ jsonCopy: Shift //TODO : check if this fails
+}
+
+//There's a difference between how objects are stored in Firebase vs in-browser. Look at note on ScheduledShift type for more info.
+export type fbScheduledShift = {
id: string
shiftID: string
date: string
@@ -91,6 +109,7 @@ export type ScheduledShift = {
verifiedAt: string
unverifiedAt: string
penaltyHours: number
+ jsonCopy: Shift //TODO : check if this fails
}
export type House = {