Skip to content

Commit

Permalink
feat: make the calendar better
Browse files Browse the repository at this point in the history
  • Loading branch information
darraghoriordan committed Sep 25, 2023
1 parent ad66825 commit 81128b2
Show file tree
Hide file tree
Showing 11 changed files with 752 additions and 27 deletions.
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module.exports = {
roots: ['<rootDir>/src'],
preset: 'ts-jest',
testRegex: '(.*.(test|spec)).(jsx?|tsx?|ts?)$',
moduleFileExtensions: ['ts', 'js', 'json'],
moduleFileExtensions: ['ts', 'js', 'json', 'tsx'],
setupFilesAfterEnv: ['./src/tests/setupTests.ts'],
collectCoverage: true,
collectCoverageFrom: [
Expand Down
317 changes: 317 additions & 0 deletions src/app/DevHistory/Calendar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,317 @@
/* eslint-disable jsx-a11y/anchor-is-valid */
import React, { Fragment, useEffect, useRef } from 'react'
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/20/solid'
import { IncrementAnalysis } from '../../electron/devHistory/models/IncrementAnalysis'
import { ScheduledItem } from './Components/ScheduleItem'

const days = [
{ date: '2021-12-27' },
{ date: '2021-12-28' },
{ date: '2021-12-29' },
{ date: '2021-12-30' },
{ date: '2021-12-31' },
{ date: '2022-01-01', isCurrentMonth: true },
{ date: '2022-01-02', isCurrentMonth: true },
{ date: '2022-01-03', isCurrentMonth: true },
{ date: '2022-01-04', isCurrentMonth: true },
{ date: '2022-01-05', isCurrentMonth: true },
{ date: '2022-01-06', isCurrentMonth: true },
{ date: '2022-01-07', isCurrentMonth: true },
{ date: '2022-01-08', isCurrentMonth: true },
{ date: '2022-01-09', isCurrentMonth: true },
{ date: '2022-01-10', isCurrentMonth: true },
{ date: '2022-01-11', isCurrentMonth: true },
{ date: '2022-01-12', isCurrentMonth: true },
{ date: '2022-01-13', isCurrentMonth: true },
{ date: '2022-01-14', isCurrentMonth: true },
{ date: '2022-01-15', isCurrentMonth: true },
{ date: '2022-01-16', isCurrentMonth: true },
{ date: '2022-01-17', isCurrentMonth: true },
{ date: '2022-01-18', isCurrentMonth: true },
{ date: '2022-01-19', isCurrentMonth: true },
{ date: '2022-01-20', isCurrentMonth: true, isToday: true },
{ date: '2022-01-21', isCurrentMonth: true },
{ date: '2022-01-22', isCurrentMonth: true, isSelected: true },
{ date: '2022-01-23', isCurrentMonth: true },
{ date: '2022-01-24', isCurrentMonth: true },
{ date: '2022-01-25', isCurrentMonth: true },
{ date: '2022-01-26', isCurrentMonth: true },
{ date: '2022-01-27', isCurrentMonth: true },
{ date: '2022-01-28', isCurrentMonth: true },
{ date: '2022-01-29', isCurrentMonth: true },
{ date: '2022-01-30', isCurrentMonth: true },
{ date: '2022-01-31', isCurrentMonth: true },
{ date: '2022-02-01' },
{ date: '2022-02-02' },
{ date: '2022-02-03' },
{ date: '2022-02-04' },
{ date: '2022-02-05' },
{ date: '2022-02-06' },
]
const times = [
'12AM',
'1AM',
'2AM',
'3AM',
'4AM',
'5AM',
'6AM',
'7AM',
'8AM',
'9AM',
'10AM',
'11AM',
'12PM',
'1PM',
'2PM',
'3PM',
'4PM',
'5PM',
'6PM',
'7PM',
'8PM',
'9PM',
'10PM',
'11PM',
]

function classNames(...classes: (string | boolean | undefined)[]) {
return classes.filter(Boolean).join(' ')
}
export function calculateTimeDegree(inputTime: Date) {
// Calculate the integer representation within the range [0, 288]
const integerRepresentation = Math.min(
Math.max(0, (inputTime.getHours() * 60 + inputTime.getMinutes()) / 5),
288,
)

return integerRepresentation
}
export default function Calendar({
analysis,
}: {
analysis: IncrementAnalysis[]
}) {
const container = useRef<HTMLDivElement | null>(null)
const containerNav = useRef<HTMLDivElement | null>(null)
const containerOffset = useRef<HTMLDivElement | null>(null)

useEffect(() => {
// Set the container scroll position based on the current time.
const currentMinute = new Date().getHours() * 60
if (
container === null ||
container.current === null ||
containerNav === null ||
containerNav.current === null ||
containerOffset === null ||
containerOffset.current === null
) {
return
}

container.current.scrollTop =
((container.current.scrollHeight -
containerNav.current.offsetHeight -
containerOffset.current.offsetHeight) *
currentMinute) /
1440
}, [analysis])

return (
<div className="flex flex-auto overflow-hidden bg-white isolate">
<div ref={container} className="flex flex-col flex-auto overflow-auto">
<div
ref={containerNav}
className="sticky top-0 z-10 flex-none text-xs text-gray-500 bg-white shadow grid grid-cols-7 ring-1 ring-black ring-opacity-5 md:hidden"
>
<button
type="button"
className="flex flex-col items-center pt-3 pb-1.5"
>
<span>W</span>
{/* Default: "text-gray-900", Selected: "bg-gray-900 text-white", Today (Not Selected): "text-indigo-600", Today (Selected): "bg-indigo-600 text-white" */}
<span className="flex items-center justify-center w-8 h-8 mt-3 text-base font-semibold text-gray-900 rounded-full">
19
</span>
</button>
<button
type="button"
className="flex flex-col items-center pt-3 pb-1.5"
>
<span>T</span>
<span className="flex items-center justify-center w-8 h-8 mt-3 text-base font-semibold text-indigo-600 rounded-full">
20
</span>
</button>
<button
type="button"
className="flex flex-col items-center pt-3 pb-1.5"
>
<span>F</span>
<span className="flex items-center justify-center w-8 h-8 mt-3 text-base font-semibold text-gray-900 rounded-full">
21
</span>
</button>
<button
type="button"
className="flex flex-col items-center pt-3 pb-1.5"
>
<span>S</span>
<span className="flex items-center justify-center w-8 h-8 mt-3 text-base font-semibold text-white bg-gray-900 rounded-full">
22
</span>
</button>
<button
type="button"
className="flex flex-col items-center pt-3 pb-1.5"
>
<span>S</span>
<span className="flex items-center justify-center w-8 h-8 mt-3 text-base font-semibold text-gray-900 rounded-full">
23
</span>
</button>
<button
type="button"
className="flex flex-col items-center pt-3 pb-1.5"
>
<span>M</span>
<span className="flex items-center justify-center w-8 h-8 mt-3 text-base font-semibold text-gray-900 rounded-full">
24
</span>
</button>
<button
type="button"
className="flex flex-col items-center pt-3 pb-1.5"
>
<span>T</span>
<span className="flex items-center justify-center w-8 h-8 mt-3 text-base font-semibold text-gray-900 rounded-full">
25
</span>
</button>
</div>
<div className="flex flex-auto w-full">
<div className="flex-none bg-white w-14 ring-1 ring-gray-100" />
<div className="flex-auto grid grid-cols-1 grid-rows-1">
{/* Horizontal lines */}
<div
className="col-start-1 col-end-2 row-start-1 grid divide-y divide-gray-100"
style={{ gridTemplateRows: 'repeat(48, minmax(3.5rem, 1fr))' }}
>
<div ref={containerOffset} className="row-end-1 h-7"></div>
{times.map(time => {
return (
<Fragment key={time}>
<div>
<div className="sticky left-0 pr-2 text-xs text-right text-gray-400 -ml-14 -mt-2.5 w-14 leading-5">
{time}
</div>
</div>
<div />
</Fragment>
)
})}
</div>

{/* Events */}
<ol
className="col-start-1 col-end-2 row-start-1 grid grid-cols-1"
style={{
gridTemplateRows: '1.75rem repeat(288, minmax(0, 1fr)) auto',
}}
>
{analysis.map((item, index) => {
if (!item.raw.analysis?.summary) {
return null
}
const startDegree = calculateTimeDegree(
item.increment.startDate,
)
const spanDegree =
calculateTimeDegree(item.increment.endDate) - startDegree

return (
<li
className="relative flex mt-px"
style={{
gridRow: `${startDegree + 1} / span ${spanDegree + 1}`,
}}
key={index}
>
<ScheduledItem item={item.raw.analysis.summary} />
</li>
)
})}
</ol>
</div>
</div>
</div>
<div className="flex-none hidden w-1/2 max-w-md px-8 py-10 border-l border-gray-100 md:block">
<div className="flex items-center text-center text-gray-900">
<button
type="button"
className="flex items-center justify-center flex-none text-gray-400 -m-1.5 p-1.5 hover:text-gray-500"
>
<span className="sr-only">Previous month</span>
<ChevronLeftIcon className="w-5 h-5" aria-hidden="true" />
</button>
<div className="flex-auto text-sm font-semibold">January 2022</div>
<button
type="button"
className="flex items-center justify-center flex-none text-gray-400 -m-1.5 p-1.5 hover:text-gray-500"
>
<span className="sr-only">Next month</span>
<ChevronRightIcon className="w-5 h-5" aria-hidden="true" />
</button>
</div>
<div className="mt-6 text-xs text-center text-gray-500 grid grid-cols-7 leading-6">
<div>M</div>
<div>T</div>
<div>W</div>
<div>T</div>
<div>F</div>
<div>S</div>
<div>S</div>
</div>
<div className="mt-2 text-sm bg-gray-200 rounded-lg shadow isolate grid grid-cols-7 gap-px ring-1 ring-gray-200">
{days.map((day, dayIdx) => (
<button
key={day.date}
type="button"
className={classNames(
'py-1.5 hover:bg-gray-100 focus:z-10',
day.isCurrentMonth ? 'bg-white' : 'bg-gray-50',
(day.isSelected || day.isToday) && 'font-semibold',
day.isSelected && 'text-white',
!day.isSelected &&
day.isCurrentMonth &&
!day.isToday &&
'text-gray-900',
!day.isSelected &&
!day.isCurrentMonth &&
!day.isToday &&
'text-gray-400',
day.isToday && !day.isSelected && 'text-indigo-600',
dayIdx === 0 && 'rounded-tl-lg',
dayIdx === 6 && 'rounded-tr-lg',
dayIdx === days.length - 7 && 'rounded-bl-lg',
dayIdx === days.length - 1 && 'rounded-br-lg',
)}
>
<time
dateTime={day.date}
className={classNames(
'mx-auto flex h-7 w-7 items-center justify-center rounded-full',
day.isSelected && day.isToday && 'bg-indigo-600',
day.isSelected && !day.isToday && 'bg-gray-900',
)}
>
{day?.date?.split('-')?.pop()?.replace(/^0/, '')}
</time>
</button>
))}
</div>
</div>
</div>
)
}
47 changes: 47 additions & 0 deletions src/app/DevHistory/Components/DiscreteDayNav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/24/outline'
import isToday from 'date-fns/isToday'

export function DiscreteDayNav({
date,
setSelectedDate,
}: {
date: Date
setSelectedDate: React.Dispatch<React.SetStateAction<Date>>
}) {
return (
<div className="relative flex items-center bg-white rounded-md shadow-sm md:items-stretch">
<button
type="button"
onClick={() => {
const newDate = new Date(date)
newDate.setDate(newDate.getDate() - 1)
setSelectedDate(newDate)
}}
className="flex items-center justify-center w-12 pr-1 text-gray-400 border-l border-gray-300 h-9 rounded-l-md border-y hover:text-gray-500 focus:relative md:w-9 md:pr-0 md:hover:bg-gray-50"
>
<span className="sr-only">Previous day</span>
<ChevronLeftIcon className="w-5 h-5" aria-hidden="true" />
</button>
<button
type="button"
className="hidden text-sm font-semibold text-gray-900 border-gray-300 border-y px-3.5 hover:bg-gray-50 focus:relative md:block"
>
{isToday(date) ? 'Today' : date.toLocaleDateString()}
</button>
<span className="relative w-px h-5 -mx-px bg-gray-300 md:hidden" />
<button
disabled={isToday(date)}
type="button"
onClick={() => {
const newDate = new Date(date)
newDate.setDate(newDate.getDate() + 1)
setSelectedDate(newDate)
}}
className="flex items-center justify-center w-12 pl-1 text-gray-400 border-r border-gray-300 h-9 rounded-r-md border-y hover:text-gray-500 focus:relative md:w-9 md:pl-0 md:hover:bg-gray-50"
>
<span className="sr-only">Next day</span>
<ChevronRightIcon className="w-5 h-5" aria-hidden="true" />
</button>
</div>
)
}
Loading

0 comments on commit 81128b2

Please sign in to comment.