This commit is contained in:
chamikaJ
2025-04-17 18:28:54 +05:30
parent f583291d8a
commit 8825b0410a
2837 changed files with 241385 additions and 127578 deletions

View File

@@ -0,0 +1,110 @@
import React from 'react';
import { Tooltip } from 'antd';
import { useAppDispatch } from '@/hooks/useAppDispatch';
import { toggleScheduleDrawer } from '../../../features/schedule/scheduleSlice';
type DayAllocationCellProps = {
totalPerDayHours: number;
loggedHours: number;
workingHours: number;
isWeekend: boolean;
};
const DayAllocationCell = ({
totalPerDayHours,
loggedHours,
workingHours,
isWeekend,
}: DayAllocationCellProps) => {
const dispatch = useAppDispatch();
// If it's a weekend, override values and disable interaction
const effectiveTotalPerDayHours = isWeekend ? 0 : totalPerDayHours;
const effectiveLoggedHours = isWeekend ? 0 : loggedHours;
const effectiveWorkingHours = isWeekend ? 1 : workingHours; // Avoid division by zero
const tooltipContent = isWeekend ? (
<div style={{ display: 'flex', flexDirection: 'column' }}>
<span>Weekend</span>
</div>
) : (
<div style={{ display: 'flex', flexDirection: 'column' }}>
<span>Total Allocation: {effectiveTotalPerDayHours + effectiveLoggedHours}h</span>
<span>Time Logged: {effectiveLoggedHours}h</span>
<span>Remaining Time: {effectiveTotalPerDayHours}h</span>
</div>
);
const gradientColor = isWeekend
? 'rgba(200, 200, 200, 0.35)' // Inactive color for weekends
: effectiveTotalPerDayHours <= 0
? 'rgba(200, 200, 200, 0.35)'
: effectiveTotalPerDayHours <= effectiveWorkingHours
? 'rgba(6, 126, 252, 0.4)'
: 'rgba(255, 0, 0, 0.4)';
return (
<div
style={{
fontSize: '14px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
padding: '10px 7px',
height: '92px',
flexDirection: 'column',
pointerEvents: isWeekend ? 'none' : 'auto',
}}
>
<Tooltip title={tooltipContent}>
<div
style={{
width: '63px',
background: `linear-gradient(to top, ${gradientColor} ${
(effectiveTotalPerDayHours * 100) / effectiveWorkingHours
}%, rgba(190, 190, 190, 0.25) ${
(effectiveTotalPerDayHours * 100) / effectiveWorkingHours
}%)`,
justifyContent: effectiveLoggedHours > 0 ? 'flex-end' : 'center',
display: 'flex',
alignItems: 'center',
height: '100%',
borderRadius: '5px',
flexDirection: 'column',
cursor: isWeekend ? 'not-allowed' : 'pointer', // Change cursor for weekends
}}
onClick={!isWeekend ? () => dispatch(toggleScheduleDrawer()) : undefined}
>
<span
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: `${(effectiveTotalPerDayHours * 100) / effectiveWorkingHours}%`,
}}
>
{effectiveTotalPerDayHours}h
</span>
{effectiveLoggedHours > 0 && (
<span
style={{
height: `${(effectiveLoggedHours * 100) / effectiveWorkingHours}%`,
backgroundColor: 'rgba(98, 210, 130, 1)',
width: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
borderBottomLeftRadius: '5px',
borderBottomRightRadius: '5px',
}}
>
{effectiveLoggedHours}h
</span>
)}
</div>
</Tooltip>
</div>
);
};
export default React.memo(DayAllocationCell);

View File

@@ -0,0 +1,286 @@
import React, { useMemo, useRef, useState } from 'react';
import { useAppSelector } from '@/hooks/useAppSelector';
import { useAppDispatch } from '@/hooks/useAppDispatch';
import { fetchDateList, fetchTeamData } from '../../../features/schedule/scheduleSlice';
import { themeWiseColor } from '../../../utils/themeWiseColor';
import GranttMembersTable from './grantt-members-table';
import { CELL_WIDTH } from '../../../shared/constants';
import { Flex, Popover } from 'antd';
import DayAllocationCell from './day-allocation-cell';
import ProjectTimelineBar from './project-timeline-bar';
import ProjectTimelineModal from '@/features/schedule/ProjectTimelineModal';
const GranttChart = React.forwardRef(({ type, date }: { type: string; date: Date }, ref) => {
const [expandedProject, setExpandedProject] = useState<string | null>(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [selectedProjectId, setSelectedProjectId] = useState<string|undefined>(undefined);
const { teamData } = useAppSelector(state => state.scheduleReducer);
const { dateList, loading, dayCount } = useAppSelector(state => state.scheduleReducer);
// get theme details from theme reducer
const themeMode = useAppSelector(state => state.themeReducer.mode);
const dispatch = useAppDispatch();
const getAllData = async () => {
await dispatch(fetchTeamData());
await dispatch(fetchDateList({ date, type }));
};
// useMemo(() => {
// dispatch(fetchTeamData());
// }, [date, type]);
useMemo(() => {
getAllData();
}, [date, type]);
// function to scroll the timeline header and body together
// refs
const timelineScrollRef = useRef<HTMLDivElement>(null);
const timelineHeaderScrollRef = useRef<HTMLDivElement>(null);
const membersScrollRef = useRef<HTMLDivElement>(null);
// Syncing scroll vertically between timeline and members
const syncVerticalScroll = (source: 'timeline' | 'members') => {
if (source === 'timeline') {
if (membersScrollRef.current && timelineScrollRef.current) {
membersScrollRef.current.scrollTop = timelineScrollRef.current.scrollTop;
}
} else {
if (timelineScrollRef.current && membersScrollRef.current) {
timelineScrollRef.current.scrollTop = membersScrollRef.current.scrollTop;
}
}
};
// syncing scroll horizontally between timeline and header
const syncHorizontalScroll = (source: 'timeline' | 'header') => {
if (source === 'timeline') {
if (timelineHeaderScrollRef.current && timelineScrollRef.current) {
timelineHeaderScrollRef.current.scrollLeft = timelineScrollRef.current.scrollLeft;
}
} else {
if (timelineScrollRef.current && timelineHeaderScrollRef.current) {
timelineScrollRef.current.scrollLeft = timelineHeaderScrollRef.current.scrollLeft;
}
}
};
const scrollToToday = () => {
if (!timelineScrollRef.current || !dateList?.date_data) return;
// Find the index of the "Today" date
let todayIndex = 0;
dateList.date_data.some((date: any) => {
const dayIndex = date.days.findIndex((day: any) => day.isToday);
if (dayIndex !== -1) {
todayIndex += dayIndex; // Add the index of today within the current month's days
return true;
}
todayIndex += date.days.length; // Increment by the number of days in the current month
return false;
});
// Calculate the scroll position
const scrollPosition = todayIndex * CELL_WIDTH;
// Scroll the timeline
timelineScrollRef.current.scrollTo({
left: scrollPosition,
});
};
React.useImperativeHandle(ref, () => ({
scrollToToday,
}));
return (
<div
style={{
display: 'grid',
gridTemplateColumns: '375px 1fr',
overflow: 'hidden',
height: 'calc(100vh - 206px)',
border: themeMode === 'dark' ? '1px solid #303030' : '1px solid #e5e7eb',
borderRadius: '4px',
backgroundColor: themeMode === 'dark' ? '#141414' : '',
}}
>
{/* teams table */}
<div
style={{
background: themeWiseColor('#fff', '#141414', themeMode),
}}
className={`after:content relative z-10 after:absolute after:-right-1 after:top-0 after:-z-10 after:h-full after:w-1.5 after:bg-transparent after:bg-gradient-to-r after:from-[rgba(0,0,0,0.12)] after:to-transparent`}
>
<GranttMembersTable
members={teamData}
expandedProject={expandedProject}
setExpandedProject={setExpandedProject}
membersScrollRef={membersScrollRef}
syncVerticalScroll={syncVerticalScroll}
/>
</div>
{/* timeline */}
<div style={{ overflow: 'auto', position: 'relative' }}>
<div
ref={timelineHeaderScrollRef}
style={{
position: 'sticky',
overflow: 'auto',
top: 0,
left: 0,
right: 0,
zIndex: 100,
backgroundColor: themeWiseColor('#fff', '#141414', themeMode),
scrollbarWidth: 'none',
borderBottom: themeMode === 'dark' ? '1px solid #303030' : '1px solid #e5e7eb',
}}
onScroll={() => syncHorizontalScroll('header')}
>
<div
style={{
display: 'grid',
gridTemplateColumns: `repeat(${dayCount}, ${CELL_WIDTH}px)`,
}}
>
{dateList?.date_data?.map((date: any, index: number) =>
date.days.map((day: any) => (
<div
key={index + day.day}
style={{
background: day.isWeekend
? 'rgba(217, 217, 217, 0.4)'
: day.isToday
? '#69b6fb'
: '',
color: day.isToday ? '#fff' : '',
padding: '8px 0',
textAlign: 'center',
height: 60,
}}
>
<div>{day.name},</div>
<div>
{date?.month.substring(0, 4)} {day.day}
</div>
</div>
))
)}
</div>
</div>
<Flex
vertical
ref={timelineScrollRef}
onScroll={() => {
syncVerticalScroll('timeline');
syncHorizontalScroll('timeline');
}}
style={{
height: 'calc(100vh - 270px)',
overflow: 'auto',
}}
>
{teamData.map((member: any) => (
<div
key={member.id}
style={{
display: 'grid',
gridTemplateColumns: `repeat(${dayCount}, ${CELL_WIDTH}px)`,
}}
>
{dateList?.date_data?.map((date: any) =>
date.days.map((day: any) => (
<div
key={`${date.month}-${day.day}`}
style={{
background: day.isWeekend ? 'rgba(217, 217, 217, 0.4)' : '',
color: day.isToday ? '#fff' : '',
height: 90,
}}
>
<DayAllocationCell
workingHours={8}
loggedHours={0}
totalPerDayHours={0}
isWeekend={day.isWeekend}
/>
</div>
))
)}
{expandedProject === member.id && (
<div>
<Popover
content={<ProjectTimelineModal memberId={member?.team_member_id} projectId={selectedProjectId} setIsModalOpen={setIsModalOpen} />}
trigger={'click'}
open={isModalOpen}
></Popover>
{member.projects.map((project: any) => (
<div
key={project.id}
onClick={() => {
if (!(project?.date_union?.start && project?.date_union?.end)) {
setSelectedProjectId(project?.id);
setIsModalOpen(true);
}
}}
style={{
display: 'grid',
gridTemplateColumns: `repeat(${dayCount}, ${CELL_WIDTH}px)`,
position: 'relative',
}}
>
<Flex
align="center"
style={{
position: 'absolute',
left: 0,
zIndex: 50,
height: 65,
}}
>
{project?.date_union?.start && project?.date_union?.end && (
<ProjectTimelineBar
defaultData={project?.default_values}
project={project}
indicatorWidth={project?.indicator_width}
indicatorOffset={project?.indicator_offset}
/>
)}
</Flex>
{dateList?.date_data?.map((date: any) =>
date.days.map((day: any) => (
<div
key={`${date.month}-${day.day}`}
style={{
background: day.isWeekend ? 'rgba(217, 217, 217, 0.4)' : '',
height: 65,
}}
>
<div
style={{ width: '100%', height: '100%' }}
className={`rounded-sm outline-1 hover:outline ${themeMode === 'dark' ? 'outline-white/10' : 'outline-black/10'}`}
></div>
</div>
))
)}
</div>
))}
</div>
)}
</div>
))}
</Flex>
</div>
</div>
);
});
export default GranttChart;

View File

@@ -0,0 +1,147 @@
import { Badge, Button, Flex, Tooltip } from 'antd';
import React, { useCallback } from 'react';
import { useAppSelector } from '@/hooks/useAppSelector';
import CustomAvatar from '../../CustomAvatar';
import { fetchMemberProjects, toggleScheduleDrawer } from '../../../features/schedule/scheduleSlice';
import { CaretDownOutlined, CaretRightFilled } from '@ant-design/icons';
import { useTranslation } from 'react-i18next';
import { useAppDispatch } from '@/hooks/useAppDispatch';
type GranttChartMembersTabelProps = {
members: any[];
expandedProject: string | null;
setExpandedProject: (id: string | null) => void;
membersScrollRef: any;
syncVerticalScroll: (source: 'timeline' | 'members') => void;
};
const GranttMembersTable = React.memo(
({
members,
expandedProject,
setExpandedProject,
membersScrollRef,
syncVerticalScroll,
}: GranttChartMembersTabelProps) => {
// localization
const { t } = useTranslation('schedule');
// get theme details
const themeMode = useAppSelector(state => state.themeReducer.mode);
const dispatch = useAppDispatch();
const handleToggleDrawer = useCallback(() => {
dispatch(toggleScheduleDrawer());
}, [dispatch]);
const handleToggleProject = useCallback(
(id: string) => {
if(expandedProject != id) {
dispatch(fetchMemberProjects({ id }));
}
setExpandedProject(expandedProject === id ? null : id);
},
[expandedProject, setExpandedProject]
);
return (
<Flex
vertical
style={{
width: 370,
marginBlockStart: 60,
borderTop: themeMode === 'dark' ? '1px solid #303030' : '1px solid #e5e7eb',
}}
>
{/* right side of the table */}
<div
id="members-header"
style={{
position: 'fixed',
top: 0,
zIndex: 100,
width: 370,
height: '60px',
backgroundColor: themeMode === 'dark' ? '#141414' : '#fff',
}}
></div>
<Flex
vertical
ref={membersScrollRef}
onScroll={() => syncVerticalScroll('members')}
style={{
maxHeight: 'calc(100vh - 278px)',
overflow: 'auto',
}}
>
{members.map(member => (
<Flex vertical key={member.id}>
<Flex
gap={8}
align="center"
justify="space-between"
style={{
paddingInline: 12,
height: 90,
}}
>
<Flex gap={8} align="center">
<CustomAvatar avatarName={member?.name} size={32} />
<Button
type="text"
size="small"
style={{ padding: 0 }}
onClick={handleToggleDrawer}
>
{member.name}
</Button>
</Flex>
<Button size="small" type="text" onClick={() => handleToggleProject(member.id)}>
{expandedProject === member.id ? <CaretDownOutlined /> : <CaretRightFilled />}
</Button>
</Flex>
{expandedProject === member.id &&
member.projects.map((project: any, index: any) => {
return (
<Flex
gap={8}
align="center"
key={index}
style={{
paddingInline: 12,
position: 'sticky',
height: 65,
}}
>
<Badge color="red" />
<Tooltip
title={
<div style={{ display: 'flex', flexDirection: 'column' }}>
<span>
{t('startDate')}: {project?.date_union?.start}
</span>
<span>
{t('endDate')}: {project?.date_union?.end}
</span>
</div>
}
>
{project.name}
</Tooltip>
</Flex>
);
})}
</Flex>
))}
</Flex>
</Flex>
);
}
);
export default GranttMembersTable;

View File

@@ -0,0 +1,166 @@
import React, { useState } from 'react';
import { Flex, Popover, Typography } from 'antd';
import { useAppDispatch } from '@/hooks/useAppDispatch';
import { useTranslation } from 'react-i18next';
import { useAppSelector } from '@/hooks/useAppSelector';
import { getWorking, toggleScheduleDrawer } from '../../../features/schedule/scheduleSlice';
import ProjectTimelineModal from '../../../features/schedule/ProjectTimelineModal';
import { Resizable } from 're-resizable';
import { themeWiseColor } from '../../../utils/themeWiseColor';
import { MoreOutlined } from '@ant-design/icons';
import { CELL_WIDTH } from '../../../shared/constants';
import { ScheduleData } from '@/types/schedule/schedule-v2.types';
type ProjectTimelineBarProps = {
project: any;
indicatorOffset: number;
indicatorWidth: number;
defaultData?: ScheduleData;
};
const ProjectTimelineBar = ({
project,
indicatorOffset,
indicatorWidth,
defaultData,
}: ProjectTimelineBarProps) => {
const [width, setWidth] = useState(indicatorWidth);
const [currentDuration, setCurrentDuration] = useState(indicatorWidth);
const [totalHours, setTotalHours] = useState(project?.total_hours);
const [leftOffset, setLeftOffset] = useState(indicatorOffset);
const [isModalOpen, setIsModalOpen] = useState(false);
const { t } = useTranslation('schedule');
const themeMode = useAppSelector(state => state.themeReducer.mode);
const dispatch = useAppDispatch();
const handleResize = (
event: MouseEvent | TouchEvent,
direction: string,
ref: HTMLElement,
delta: { width: number; height: number }
) => {
let newWidth = width;
let newLeftOffset = leftOffset;
if (direction === 'right') {
newWidth = Math.max(CELL_WIDTH, width + delta.width);
if (newWidth <= CELL_WIDTH * 30) {
setWidth(newWidth);
const newDuration = Math.round(newWidth / CELL_WIDTH);
setCurrentDuration(newDuration);
setTotalHours(newDuration * project?.hours_per_day);
}
} else if (direction === 'left') {
const deltaWidth = Math.min(leftOffset, delta.width);
newLeftOffset = leftOffset - deltaWidth;
newWidth = width + deltaWidth;
if (newLeftOffset >= 0 && newWidth >= CELL_WIDTH && newWidth <= CELL_WIDTH * 30) {
setLeftOffset(newLeftOffset);
setWidth(newWidth);
const newDuration = Math.round(newWidth / CELL_WIDTH);
setCurrentDuration(newDuration);
setTotalHours(newDuration * project?.hours_per_day);
}
}
};
return (
<Popover
content={<ProjectTimelineModal defaultData={defaultData} projectId={project?.id} setIsModalOpen={setIsModalOpen} />}
trigger={'click'}
open={isModalOpen}
>
<Resizable
size={{ width, height: 56 }}
onResizeStop={(e, direction, ref, delta) =>
handleResize(e, direction as 'left' | 'right', ref, delta)
}
minWidth={CELL_WIDTH}
maxWidth={CELL_WIDTH * 30}
grid={[CELL_WIDTH, 1]}
enable={{
top: false,
right: true,
bottom: false,
left: true,
topRight: false,
bottomRight: false,
bottomLeft: false,
topLeft: false,
}}
handleComponent={{
right: <MoreOutlined style={{ fontSize: 24, color: 'white' }} />,
left: <MoreOutlined style={{ fontSize: 24, color: 'white' }} />,
}}
handleClasses={{
right:
'hidden group-hover:flex -translate-x-[5px] bg-[#1890ff] px-1 justify-center rounded-tr rounded-br',
left: 'hidden group-hover:flex translate-x-[5px] bg-[#1890ff] px-1 justify-center rounded-tl rounded-bl',
}}
className="group hover:shadow-md"
style={{
marginInlineStart: leftOffset,
backgroundColor: themeWiseColor(
'rgba(240, 248, 255, 1)',
'rgba(0, 142, 204, 0.5)',
themeMode
),
borderRadius: 6,
border: `1px solid ${themeWiseColor(
'rgba(149, 197, 248, 1)',
'rgba(24, 144, 255, 1)',
themeMode
)}`,
display: 'flex',
alignItems: 'center',
flexDirection: 'column',
justifyContent: 'center',
padding: '4px 10px',
zIndex: 99,
cursor: 'pointer',
}}
>
<Flex
vertical
align="center"
justify="center"
style={{ width: '100%' }}
onClick={() => {setIsModalOpen(true);dispatch(getWorking());}}
>
<Typography.Text
style={{
fontSize: '12px',
fontWeight: 'bold',
}}
ellipsis={{ expanded: false }}
>
{t('total')} {totalHours}h
</Typography.Text>
{currentDuration > 1 && (
<Typography.Text style={{ fontSize: '10px' }} ellipsis={{ expanded: false }}>
{t('perDay')} {project?.hours_per_day}h
</Typography.Text>
)}
<Typography.Text
style={{
fontSize: '10px',
textDecoration: 'underline',
width: 'fit-content',
}}
ellipsis={{ expanded: false }}
onClick={e => {
e.stopPropagation();
dispatch(toggleScheduleDrawer());
}}
>
20 {t('tasks')}
</Typography.Text>
</Flex>
</Resizable>
</Popover>
);
};
export default React.memo(ProjectTimelineBar);

View File

@@ -0,0 +1,72 @@
import { TaskType } from '@/types/task.types';
import { useAppSelector } from '@/hooks/useAppSelector';
import GroupByFilterDropdown from '@/components/project-task-filters/filter-dropdowns/group-by-filter-dropdown';
import { useTranslation } from 'react-i18next';
import StatusGroupTables from '@/pages/projects/project-view-1/taskList/statusTables/StatusGroupTables';
import PriorityGroupTables from '@/pages/projects/projectView/taskList/groupTables/priorityTables/PriorityGroupTables';
import { IProjectTask } from '@/types/project/projectTasksViewModel.types';
import { ITaskListGroup } from '@/types/tasks/taskList.types';
const WithStartAndEndDates = () => {
const dataSource: ITaskListGroup[] = useAppSelector(state => state.taskReducer.taskGroups);
const { t } = useTranslation('schedule');
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '20px' }}>
<div
style={{
display: 'flex',
gap: '5px',
flexDirection: 'column',
border: '1px solid rgba(0, 0, 0, 0.21)',
padding: '20px',
borderRadius: '15px',
}}
>
<span style={{ fontSize: '24px', fontWeight: 'bold', color: 'rgba(112, 113, 114, 1)' }}>
2024-11-04 - 2024-12-24
</span>
<div
style={{
display: 'flex',
alignItems: 'center',
width: '100%',
gap: '200px',
color: 'rgba(121, 119, 119, 1)',
}}
>
<div style={{ width: '50%' }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<span>{t('allocatedTime')}</span>
<span>8 {t('hours')}</span>
</div>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<span>{t('totalLogged')}</span>
<span>7 {t('hours')}</span>
</div>
</div>
<div style={{ width: '50%' }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<span>{t('loggedBillable')}</span>
<span>5 {t('hours')}</span>
</div>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<span>{t('loggedNonBillable')}</span>
<span>2 {t('hours')}</span>
</div>
</div>
</div>
</div>
<div>
<GroupByFilterDropdown />
</div>
<div>
{dataSource.map(group => (
<StatusGroupTables key={group.id} group={group} />
))}
{/* <PriorityGroupTables datasource={dataSource} /> */}
</div>
</div>
);
};
export default WithStartAndEndDates;

View File

@@ -0,0 +1,108 @@
import React, { useMemo, useState } from 'react';
import { Flex, Typography } from 'antd';
import { Member } from '../../../types/schedule/schedule.types';
import DayAllocationCell from './day-allocation-cell';
import { CELL_WIDTH } from '../../../shared/constants';
type DatesType = {
date_data: {
month: string;
weeks: any[];
days: {
day: number;
name: string;
isWeekend: boolean;
isToday: boolean;
}[];
}[];
chart_start: Date | null;
chart_end: Date | null;
};
const Timeline = () => {
const [dates, setDates] = useState<DatesType | null>(null);
const [members, setMembers] = useState<Member[]>([]);
useMemo(() => {
const fetchData = async () => {
try {
const response = await fetch('/scheduler-data/TeamData.json');
const data = await response.json();
setMembers(data);
} catch (error) {
console.error('Error fetching data:', error);
}
};
fetchData();
}, []);
useMemo(() => {
const fetchData = async () => {
try {
const response = await fetch('/scheduler-data/scheduler-timeline-dates.json');
const data = await response.json();
setDates(data);
} catch (error) {
console.error('Error fetching data:', error);
}
};
fetchData();
}, []);
const getDaysBetween = (start: Date | null, end: Date | null): number => {
const validStart = start ? new Date(start) : new Date();
const validEnd = end ? new Date(end) : new Date();
if (
validStart instanceof Date &&
!isNaN(validStart.getTime()) &&
validEnd instanceof Date &&
!isNaN(validEnd.getTime())
) {
const oneDay = 24 * 60 * 60 * 1000;
return Math.round(Math.abs((validStart.getTime() - validEnd.getTime()) / oneDay));
} else {
console.error('Invalid date(s)');
return 0;
}
};
const numberOfDays =
dates?.chart_start && dates?.chart_end ? getDaysBetween(dates.chart_start, dates.chart_end) : 0;
return (
<div
style={{
display: 'grid',
gridTemplateColumns: `repeat(${numberOfDays + 1}, ${CELL_WIDTH}px)`,
}}
>
{dates?.date_data?.map((month, monthIndex) =>
month.days.map((day, dayIndex) => (
<div
key={`${monthIndex}-${dayIndex}`}
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
height: 60,
background: day.isWeekend
? 'rgba(217, 217, 217, 0.4)'
: day.isToday
? 'rgba(24, 144, 255, 1)'
: '',
}}
>
<Typography.Text>{day.name},</Typography.Text>
<Typography.Text>
{month.month.substring(0, 3)} {day.day}
</Typography.Text>
</div>
))
)}
</div>
);
};
export default Timeline;