// Copyright (c) 2019 Shellyl_N and Authors // license: ISC // https://github.com/shellyln import React from 'react'; import { connect } from 'react-redux'; import { RouteComponentProps } from 'react-router-dom'; import { makeStyles, useTheme } from '@material-ui/core/styles'; import Typography from '@material-ui/core/Typography'; import IconButton from '@material-ui/core/IconButton'; import ChevronLeftIcon from '@material-ui/icons/ChevronLeft'; import ChevronRightIcon from '@material-ui/icons/ChevronRight'; import SkipPreviousIcon from '@material-ui/icons/SkipPrevious'; import SkipNextIcon from '@material-ui/icons/SkipNext'; import CalendarTodayIcon from '@material-ui/icons/CalendarToday'; import clsx from 'clsx'; import { KanbanRecord, CalendarState, KanbanBoardState, KanbanBoardRecord } from '../types'; import { parseISODate } from '../lib/datetime'; import { mapDispatchToProps, mapStateToProps, CalendarActions } from '../dispatchers/CalendarViewDispatcher'; import KanbanDialog from '../components/KanbanDialog'; import TextInputDialog from '../components/TextInputDialog'; import { getConstructedAppStore } from '../store'; import './CalendarView.css'; type CalendarItemProps = CalendarState & CalendarActions & { kanbanBoardState: KanbanBoardState } & { board: KanbanBoardRecord, record: KanbanRecord, }; type CalendarViewProps = CalendarState & CalendarActions & { kanbanBoardState: KanbanBoardState } & RouteComponentProps<{id: string}> & { }; const useStyles = makeStyles(theme => ({ root: { flexGrow: 1, color: 'var(--main-text-color)', backgroundColor: 'var(--main-bg-color)', }, calendar: { width: 'calc(100% - 30px)', height: '100%', margin: '15px', border: 'solid 1px var(--main-text-color)', borderCollapse: 'collapse', tableLayout: 'fixed', }, calendarTitle: { position: 'relative', height: '30px', }, calendarTitleToolBar: { position: 'absolute', top: 0, left: 0, margin: '3px 3px 6px 4px', overflow: 'hidden', width: 'calc(100% - 30px)', height: '39px', }, calendarTopLeftHeader: { border: 'solid 1px var(--main-text-color)', borderCollapse: 'collapse', padding: '5px', }, calendarDaysHeader: { border: 'solid 1px var(--main-text-color)', borderCollapse: 'collapse', padding: '5px', width: '14%', }, calendarSundayHeader: { border: 'solid 1px var(--main-text-color)', borderCollapse: 'collapse', padding: '5px', width: '14%', backgroundColor: 'var(--weak-header-bg-color)', color: 'var(--calendar-sunday-color)', }, calendarSaturdayHeader: { border: 'solid 1px var(--main-text-color)', borderCollapse: 'collapse', padding: '5px', width: '14%', backgroundColor: 'var(--weak-header-bg-color)', color: 'var(--calendar-saturday-color)', }, calendarCell: { border: 'solid 1px var(--main-text-color)', borderCollapse: 'collapse', padding: '5px', verticalAlign: 'top', }, calendarWeekendCell: { border: 'solid 1px var(--main-text-color)', borderCollapse: 'collapse', padding: '5px', verticalAlign: 'top', backgroundColor: 'var(--weak-data-bg-color)', }, calendarCellCaptionToday: { color: 'var(--calendar-today-color)', backgroundColor: 'var(--calendar-today-bg-color)', borderRadius: '10px', border: 'solid 1.5px var(--calendar-today-bg-color)', }, chip: { fontSize: 'smaller', borderRadius: '5px', backgroundColor:'var(--sticky-blue-color)', color: 'var(--main-text-color)', width: '100%', overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis', marginBottom: '2px', paddingLeft: '3px', paddingRight: '3px', }, chipWrap: { '&:hover': { cursor: 'pointer', } } })); const CalendarItem_: React.FC = (props) => { const classes = useStyles(); const [open, setOpen] = React.useState(false); const teamOrStory = props.board.teamOrStories.find(x => x.value === props.record.teamOrStory); const taskStatus = props.board.taskStatuses.find(x => x.value === props.record.taskStatus); const now = new Date(); const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); const dueDate = props.record.dueDate ? parseISODate(props.record.dueDate) : null; const expired = (! (taskStatus && taskStatus.completed)) && (dueDate ? dueDate < today : false); const archived = props.record.flags && props.record.flags.includes('Archived'); function handleEditApply(rec: KanbanRecord) { props.updateSticky(rec); setOpen(false); } function handleArchive(id: string) { props.archiveSticky(id); setOpen(false); } function handleUnarchive(id: string) { props.unarchiveSticky(id); setOpen(false); } function handleDelete(id: string) { props.deleteSticky(id); setOpen(false); } function handleEditCancel() { setOpen(false); } return ( <> {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */} setOpen(true)}>
{props.record.description.trim().replace(/\n/g, ' ').replace(/#+/g, '').trim()}
{open ? : <> } ); } const CalendarItem = connect(mapStateToProps, mapDispatchToProps)(CalendarItem_); const CalendarView: React.FC = (props) => { const classes = useStyles(); const theme = useTheme(); const [textInputOpen, setTextInputOpen] = React.useState({ open: false, title: '', message: '', fieldLabel: '', value: '', validator: (value: string) => value.trim().length <= 0, onClose: handleCloseDialogEditBoardName, }); function handleClickEditBoardName() { const currentState = getConstructedAppStore().getState(); setTextInputOpen(Object.assign({}, textInputOpen, { open: true, title: 'Edit board name', message: '', fieldLabel: 'Board name', value: currentState.kanbanBoard.activeBoard.name, })); } function handleCloseDialogEditBoardName(apply: boolean, value?: string) { setTextInputOpen(Object.assign({}, textInputOpen, { open: false })); if (apply && value) { const currentState = getConstructedAppStore().getState(); props.updateBoardName({ boardId: currentState.kanbanBoard.activeBoardId, boardName: value }); } } if (props.match.params.id) { if (props.kanbanBoardState.activeBoard._id !== props.match.params.id) { const index = props.kanbanBoardState.boards.findIndex(x => x._id === props.match.params.id); props.changeActiveBoard(props.match.params.id); return (
{index < 0 ? <> No boards found. {props.history.push('/')}} > Click here to show main board. : <> }
); } } const now = new Date(); const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); const month = props.activeMonth; const firstDateOfMonth = new Date(month.getFullYear(), month.getMonth(), 1); const lastDateOfMonth = new Date(month.getFullYear(), month.getMonth() + 1, 0); const firstDateOfCal = new Date( firstDateOfMonth.getFullYear(), firstDateOfMonth.getMonth(), 1 - firstDateOfMonth.getDay()); const lastDateOfCal = new Date( lastDateOfMonth.getFullYear(), lastDateOfMonth.getMonth(), lastDateOfMonth.getDate() + (6 - lastDateOfMonth.getDay())); const nextCalDate = new Date( lastDateOfCal.getFullYear(), lastDateOfCal.getMonth(), lastDateOfCal.getDate() + 1); const dates: Array = []; let week: Date[] = []; dates.push(week); for (let wd = firstDateOfCal; wd.getTime() <= lastDateOfCal.getTime(); wd = new Date(wd.getFullYear(), wd.getMonth(), wd.getDate() + 1)) { if (7 <= week.length) { week = []; dates.push(week); } week.push(wd); } const stickys = props.kanbanBoardState.activeBoard.records.filter(x => { if (!x.dueDate) { return false; } const d = new Date(x.dueDate); if (firstDateOfCal <= d && d < nextCalDate) { return true; } else { return false; } }); return (
props.showToday()}> Today props.showPreviousYear()}> props.showPreviousMonth()}> props.showNextMonth()}> props.showNextYear()}> { props.activeMonth.toLocaleDateString( (navigator as any).userLanguage || navigator.language || (navigator as any).browserLanguage || 'en', {year: 'numeric', month: 'long'}) } { props.kanbanBoardState.activeBoard.name}
{dates.map(week => {week.map((wd: Date, index: number) => { const next = new Date(wd.getFullYear(), wd.getMonth(), wd.getDate() + 1); return ( ); })} )}
Sun Mon Tue Wed Thu Fri Sat
{wd.getDate()}
{stickys.filter(x => { const d = new Date(x.dueDate); if (wd <= d && d < next) { return true; } else { return false; } }).map(x => )}
{textInputOpen.open ? : <> }
); } export default connect(mapStateToProps, mapDispatchToProps)(CalendarView);