From 43be1abf96d00c495e9f251605275d8be6331fca Mon Sep 17 00:00:00 2001 From: Ahmed Al-Taiar Date: Sat, 21 Sep 2024 14:13:53 -0400 Subject: [PATCH] Add date picker to project form --- .../migration.sql | 2 + api/db/schema.prisma | 2 +- web/package.json | 1 + web/src/components/DatePicker/DatePicker.tsx | 103 ++++++++++++++++++ .../components/Project/Project/Project.tsx | 10 +- .../Project/ProjectForm/ProjectForm.tsx | 73 +++++++++++-- .../components/Project/Projects/Projects.tsx | 10 +- web/src/lib/formatters.tsx | 4 +- yarn.lock | 8 ++ 9 files changed, 197 insertions(+), 16 deletions(-) create mode 100644 api/db/migrations/20240921164727_project_date_is_mandatory_default_today_12_am/migration.sql create mode 100644 web/src/components/DatePicker/DatePicker.tsx diff --git a/api/db/migrations/20240921164727_project_date_is_mandatory_default_today_12_am/migration.sql b/api/db/migrations/20240921164727_project_date_is_mandatory_default_today_12_am/migration.sql new file mode 100644 index 0000000..d209689 --- /dev/null +++ b/api/db/migrations/20240921164727_project_date_is_mandatory_default_today_12_am/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Project" ALTER COLUMN "date" DROP DEFAULT; diff --git a/api/db/schema.prisma b/api/db/schema.prisma index b85c0dc..be66c69 100644 --- a/api/db/schema.prisma +++ b/api/db/schema.prisma @@ -69,7 +69,7 @@ model Project { title String description String @default("No description provided") images ProjectImage[] - date DateTime @default(now()) + date DateTime links String[] @default([]) tags Tag[] } diff --git a/web/package.json b/web/package.json index fad7d90..3c00c14 100644 --- a/web/package.json +++ b/web/package.json @@ -27,6 +27,7 @@ "@uppy/progress-bar": "^4.0.0", "@uppy/react": "^4.0.1", "@uppy/tus": "^4.0.0", + "date-fns": "^4.1.0", "humanize-string": "2.1.0", "react": "18.3.1", "react-colorful": "^5.6.1", diff --git a/web/src/components/DatePicker/DatePicker.tsx b/web/src/components/DatePicker/DatePicker.tsx new file mode 100644 index 0000000..39e1c43 --- /dev/null +++ b/web/src/components/DatePicker/DatePicker.tsx @@ -0,0 +1,103 @@ +import { mdiChevronLeft, mdiChevronRight } from '@mdi/js' +import Icon from '@mdi/react' +import { + add, + eachDayOfInterval, + endOfMonth, + endOfWeek, + format, + getDay, + isEqual, + isSameMonth, + isToday, + parse, + startOfWeek, + sub, +} from 'date-fns' + +interface DatePickerProps { + date: Date + setDate: React.Dispatch> + month: string + setMonth: React.Dispatch> +} + +const DatePicker = ({ date, setDate, month, setMonth }: DatePickerProps) => { + const currentMonthFirstDay = parse(month, 'MMMM yyyy', new Date()) + + const days = eachDayOfInterval({ + start: startOfWeek(currentMonthFirstDay), + end: endOfWeek(endOfMonth(currentMonthFirstDay)), + }) + + return ( +
+
+ + +

{format(currentMonthFirstDay, 'MMMM yyyy')}

+ + +
+
+ {['S', 'M', 'T', 'W', 'T', 'F', 'S '].map((weekday, i) => ( +
{weekday}
+ ))} +
+
+ {days.map((day, i) => { + const selected = isEqual(day, date) + const isCurrentMonth = isSameMonth(day, currentMonthFirstDay) + const today = isToday(day) + const weekday = getDay(day) + + let btnColor = 'btn-ghost' + + if (!isCurrentMonth) btnColor += ' opacity-40' + + if (today) btnColor = 'btn-error' + if (selected) btnColor = 'btn-primary' + + return ( +
+ +
+ ) + })} +
+
+ ) +} + +export default DatePicker diff --git a/web/src/components/Project/Project/Project.tsx b/web/src/components/Project/Project/Project.tsx index 1eb287f..685648a 100644 --- a/web/src/components/Project/Project/Project.tsx +++ b/web/src/components/Project/Project/Project.tsx @@ -74,9 +74,15 @@ const Project = ({ project }: Props) => { Links {project.links.map((link, i) => ( -
+ {link} -
+ ))} diff --git a/web/src/components/Project/ProjectForm/ProjectForm.tsx b/web/src/components/Project/ProjectForm/ProjectForm.tsx index 9a04eb5..dc11e01 100644 --- a/web/src/components/Project/ProjectForm/ProjectForm.tsx +++ b/web/src/components/Project/ProjectForm/ProjectForm.tsx @@ -1,7 +1,8 @@ import { useEffect, useMemo, useRef, useState } from 'react' -import { mdiFormatTitle, mdiLinkVariant } from '@mdi/js' +import { mdiCalendar, mdiFormatTitle, mdiLinkVariant } from '@mdi/js' import Icon from '@mdi/react' +import { format, isAfter, startOfToday } from 'date-fns' import type { EditProjectById, UpdateProjectInput } from 'types/graphql' import type { RWGqlError } from '@redwoodjs/forms' @@ -15,6 +16,7 @@ import { } from '@redwoodjs/forms' import { toast } from '@redwoodjs/web/toast' +import DatePicker from 'src/components/DatePicker/DatePicker' import FormTextList from 'src/components/FormTextList/FormTextList' type FormProject = NonNullable @@ -29,8 +31,15 @@ interface ProjectFormProps { } const ProjectForm = (props: ProjectFormProps) => { + const today = startOfToday() + const [links, setLinks] = useState(props.project?.links || []) const [linkErrors, setLinkErrors] = useState([]) + const [pickerVisible, setPickerVisible] = useState(false) + const [date, setDate] = useState( + props.project?.date ? new Date(props.project.date) : today + ) + const [month, setMonth] = useState(format(today, 'MMMM yyyy')) const urlRegex = useMemo( () => @@ -51,7 +60,7 @@ const ProjectForm = (props: ProjectFormProps) => { if (emptyCount > 0) return toast.error(`${emptyCount} links empty`) data.links = links - data.date = new Date().toISOString() // TODO: change to date picker value + data.date = date.toISOString() props.onSave(data, props?.project?.id) } @@ -115,14 +124,58 @@ const ProjectForm = (props: ProjectFormProps) => { - +
+ +
+ + {pickerVisible && ( +
+ +
+ )} + +
+ +
+ + {isAfter(date, today) && ( +
+

Project will be marked as

+
planned
+
+ )}