diff --git a/README.md b/README.md
index f4d6153..aa4c467 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,4 @@
-
-
-
📅 React Material Scheduler
developed with @mui
@@ -17,80 +14,124 @@
React mui scheduler is a react component based on @mui v5 that allows you to manage data in a calendar.
-## 🗣️ Installation
+## 🚀 Installation
```nodejs
npm install react-mui-scheduler
```
## 💻 Usage
```javascript
- import React from 'react'
- import ReactDOM from 'react-dom'
- import Scheduler from "react-mui-scheduler"
-
- function App() {
- const events = [
- {
- id: "event-1",
- label: "Consultation médicale",
- title: "Dr Shaun Murphy",
- color: "#f28f6a",
- startHour: "9 AM",
- endHour: "10 AM",
- date: "2021-09-09",
- createdAt: new Date(),
- createdBy: "Kristina Mayer"
- },
- {
- id: "event-2",
- label: "Consultation médicale",
- title: "Dr Claire Brown",
- color: "#099ce5",
- startHour: "9 AM",
- endHour: "10 AM",
- date: "2021-09-09",
- createdAt: new Date(),
- createdBy: "Kristina Mayer"
- },
- {
- id: "event-3",
- label: "Consultation médicale",
- title: "Dr Menlendez Hary",
- color: "#263686",
- startHour: "13 AM",
- endHour: "14 AM",
- date: "2021-09-12",
- createdAt: new Date(),
- createdBy: "Kristina Mayer"
- },
- ]
-
- const onCellClick = (event, row, day) => {
- // Do something...
- }
-
- const onEventClick = (event, task) => {
- // Do something...
+import React, {useState} from 'react'
+import ReactDOM from 'react-dom'
+import Scheduler from "react-mui-scheduler"
+
+function App() {
+ const [state, setState] = useState({
+ options: {
+ transitionMode: "zoom",
+ startWeekOn: "Mon",
+ defaultMode: "week"
+ },
+ alertProps: {
+ open: true,
+ color: "info",
+ severity: "info",
+ message: "🚀 Let's start with awesome react-mui-scheduler 🔥 🔥 🔥" ,
+ showActionButton: true,
+ },
+ toolbarProps: {
+ showSearchBar: true,
+ showSwitchModeButtons: true,
+ showDatePicker: true
}
-
- const onEventsChange = (item) => {
- // Do something...
+ })
+
+ const events = [
+ {
+ id: "event-1",
+ label: "Consultation médicale",
+ groupLabel: "Dr Shaun Murphy",
+ user: "Dr Shaun Murphy",
+ color: "#f28f6a",
+ startHour: "04:00 AM",
+ endHour: "05:00 AM",
+ date: "2021-09-28",
+ createdAt: new Date(),
+ createdBy: "Kristina Mayer"
+ },
+ {
+ id: "event-2",
+ label: "Consultation médicale",
+ groupLabel: "Dr Claire Brown",
+ user: "Dr Claire Brown",
+ color: "#099ce5",
+ startHour: "09:00 AM",
+ endHour: "10:00 AM",
+ date: "2021-09-29",
+ createdAt: new Date(),
+ createdBy: "Kristina Mayer"
+ },
+ {
+ id: "event-3",
+ label: "Consultation médicale",
+ groupLabel: "Dr Menlendez Hary",
+ user: "Dr Menlendez Hary",
+ color: "#263686",
+ startHour: "13 AM",
+ endHour: "14 AM",
+ date: "2021-09-30",
+ createdAt: new Date(),
+ createdBy: "Kristina Mayer"
+ },
+ {
+ id: "event-4",
+ label: "Consultation prénatale",
+ groupLabel: "Dr Shaun Murphy",
+ user: "Dr Shaun Murphy",
+ color: "#f28f6a",
+ startHour: "08:00 AM",
+ endHour: "09:00 AM",
+ date: "2021-10-01",
+ createdAt: new Date(),
+ createdBy: "Kristina Mayer"
}
+ ]
- return (
-
- )
+ const handleCellClick = (event, row, day) => {
+ // Do something...
}
-
- ReactDOM.render(, document.querySelector('#app'))
+
+ const handleEventClick = (event, task) => {
+ // Do something...
+ }
+
+ const handleEventsChange = (item) => {
+ // Do something...
+ }
+
+ const handleAlertCloseButtonClicked = (item) => {
+ // Do something...
+ setState({
+ ...state,
+ alertProps: {...state.alertProps, open: false}
+ })
+ }
+
+ return (
+
+ )
+}
+
+ReactDOM.render(, document.querySelector('#yourComponentRootId'))
```
@@ -109,16 +150,18 @@ React mui scheduler is a react component based on @mui v5 that allows you to man
## 🙇♂️ Extra
- 😊 Do you like this library ? Buy me a coffee
+ Do you like this library ? Buy me a coffee
-* Btc address: `1A2VNHSLGDyYsKWniJBe8cCqYWC52NvNZx`
+* Btc address: `bc1qettgagenn9nc8ks7ghntjfme96yvvkfhntk774`
-* Eth address: `0xFe444a01D9494Ec04f61797e15193C8016D666A5`
+* Eth address: `0xB0413d8D0336E263e289A915c383e152155881E0`
## 🔥 Some features to add in next releases
-- 👉 Week and day mode switch view
+- ✅ Week mode switch view
+
+- 👉 Day mode switch view
- 👉 Option menu
diff --git a/package.json b/package.json
index c02a340..2517d8e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "react-mui-scheduler",
- "version": "1.0.4",
+ "version": "1.1.0",
"description": "\uD83D\uDCC5 React mui scheduler is a react component based on @mui v5 that allows you to manage data in a calendar",
"main": "dist/index.esm.js",
"directories": {
@@ -18,6 +18,10 @@
],
"author": "rouftom",
"license": "MIT",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/rouftom/react-mui-scheduler.git"
+ },
"peerDependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2"
diff --git a/src/MonthModeView.jsx b/src/MonthModeView.jsx
index e923932..d82ffca 100644
--- a/src/MonthModeView.jsx
+++ b/src/MonthModeView.jsx
@@ -15,7 +15,7 @@ const StyledTableCell = styled(TableCell)(({ theme }) => ({
borderTop: `1px solid #ccc !important`,
borderBottom: `1px solid #ccc !important`,
borderLeft: `1px solid #ccc !important`,
- '&:nth-child(1)': {
+ '&:nth-of-type(1)': {
borderLeft: `0px !important`
}
},
@@ -23,12 +23,13 @@ const StyledTableCell = styled(TableCell)(({ theme }) => ({
fontSize: 14,
height: 96,
width: 64,
+ maxWidth: 64,
cursor: 'pointer',
borderLeft: `1px solid #ccc`,
- '&:nth-child(7n+1)': {
+ '&:nth-of-type(7n+1)': {
borderLeft: 0
},
- '&:nth-child(even)': {
+ '&:nth-of-type(even)': {
backgroundColor: theme.palette.action.hover
},
},
@@ -73,7 +74,19 @@ function MonthModeView (props) {
*/
const onCellDragStart = (e, item, rowIndex) => {
setState({...state, itemTransfert: {item, rowIndex}})
- //e.dataTransfer.setData('text/plain', `${item.id}_${rowIndex}`)
+ }
+
+ /**
+ * @name onCellDragEnter
+ * @description
+ * @param e
+ * @param elementId
+ * @param rowIndex
+ * @return void
+ */
+ const onCellDragEnter = (e, elementId, rowIndex) => {
+ e.preventDefault()
+ setState({...state, transfertTarget: {elementId, rowIndex}})
}
/**
@@ -98,20 +111,21 @@ function MonthModeView (props) {
let splittedDate = transfert?.item?.date?.split('-')
if (!transfert?.item?.day) {
- // Jour de la date du début du mois en chiffre
+ // Get day of the date (DD)
transfert.item.day = parseInt(splittedDate[2])
}
if (transfert.item.day !== day?.day) {
let itemCheck = day.data.findIndex(item => (
- item.day === transfert.item.day && item.title === transfert.item.title
+ item.day === transfert.item.day && item.label === transfert.item.label
))
if (itemCheck === -1) {
let prevDayEvents = rowsCopy[transfert.rowIndex].days.find(d => d.day === transfert.item.day)
let itemIndexToRemove = prevDayEvents?.data?.findIndex(i => i.id === transfert.item.id)
-
+
if (itemIndexToRemove === undefined || itemIndexToRemove === -1) {
+ console.log(prevDayEvents)
return console.log("item to remove is not found")
}
@@ -119,26 +133,13 @@ function MonthModeView (props) {
transfert.item.day = day?.day
transfert.item.date = format(day?.date, 'yyyy-MM-dd')
day.data.push(transfert.item)
- setState({...state, rows: rowsCopy})
+ setState({...state, rows: rowsCopy, itemTransfert: null, transfertTarget: null})
}
}
}
}
}
- /**
- * @name onCellDragEnter
- * @description
- * @param e
- * @param elementId
- * @param rowIndex
- * @return void
- */
- const onCellDragEnter = (e, elementId, rowIndex) => {
- e.preventDefault()
- setState({...state, transfertTarget: {elementId, rowIndex}})
- }
-
/**
* @name handleCellClick
* @description
@@ -164,7 +165,13 @@ function MonthModeView (props) {
*/
const renderTask = (tasks = [], rowId) => {
return tasks?.map((task, index) => (
- ((searchResult && task?.title === searchResult?.title) || !searchResult) && (
+ (
+ (
+ searchResult &&
+ (task?.groupLabel === searchResult?.groupLabel || task?.user === searchResult?.user)
+ ) || !searchResult
+ ) &&
+ (
handleTaskClick(e, task)}
@@ -181,7 +188,7 @@ function MonthModeView (props) {
onDragStart={e => onCellDragStart(e, task, rowId)}
>
- {task?.title}
+ {task?.label}
)
@@ -205,6 +212,7 @@ function MonthModeView (props) {
if (state?.rows) {
onEventsChange(Object.assign({}, state?.itemTransfert?.item))
}
+ // eslint-disable-next-line
}, [state?.rows, state?.itemTransfert])
return (
diff --git a/src/Scheduler.jsx b/src/Scheduler.jsx
index e7a7738..1dcfb09 100644
--- a/src/Scheduler.jsx
+++ b/src/Scheduler.jsx
@@ -1,13 +1,15 @@
import React, {useState, useEffect} from 'react'
import PropTypes from "prop-types"
-import { Grid, Paper } from "@mui/material"
+import { Grid, Paper, Fade, Zoom } from "@mui/material"
import {ThemeProvider} from "@mui/system"
import { useTheme } from '@mui/material/styles'
import {
- format, getDaysInMonth, getDay, sub, startOfMonth, isSameDay, parse
+ format, getDaysInMonth, getDay, sub, startOfMonth, isSameDay, parse,
+ add, startOfDay, startOfWeek, getWeeksInMonth
} from 'date-fns'
import SchedulerToolbar from "./Toolbar.jsx"
import MonthModeView from "./MonthModeView.jsx"
+import WeekModeView from "./WeekModeView.jsx"
/**
@@ -19,64 +21,37 @@ import MonthModeView from "./MonthModeView.jsx"
function Scheduler(props) {
const {
events,
+ options,
onCellClick,
onTaskClick,
onEventsChange,
- openAlert,
alertMessage,
- alertProps
+ alertProps,
+ onAlertCloseButtonClicked,
+ toolbarProps
} = props
const today = new Date()
const theme = useTheme()
- const weekDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
+ const TransitionMode = options?.transitionMode === 'zoom' ? Zoom : Fade
const [state, setState] = useState({})
const [searchResult, setSearchResult] = useState()
- const [mode, setMode] = useState('month')
+ const [mode, setMode] = useState(options?.defaultMode || 'month')
const [selectedDay, setSelectedDay] = useState(today)
const [daysInMonth, setDaysInMonth] = useState(getDaysInMonth(today))
const [selectedDate, setSelectedDate] = useState(format(today, 'MMMM-yyyy'))
/**
- * @name handleDateChange
- * @description
- * @param day
- * @param date
- * @return void
- */
- const handleDateChange = (day, date) => {
- setDaysInMonth(day)
- setSelectedDay(date)
- setSelectedDate(format(date, 'MMMM-yyyy'))
- setState({rows: getRows(), columns: getHeader()})
- }
-
- /**
- * @name handleModeChange
- * @description
- * @param newMode
- * @return void
- */
- const handleModeChange = (newMode) => {
- setMode(newMode)
- }
-
- /**
- * @name onSearchResult
- * @description
- * @param item
- * @return void
- */
- const onSearchResult = (item) => {
- setSearchResult(item)
- }
-
- /**
- * @name getHeader
+ * @name getMonthHeader
* @description
* @return {{headerClassName: string, headerAlign: string, headerName: string, field: string, flex: number, editable: boolean, id: string, sortable: boolean, align: string}[]}
*/
- const getHeader = () => {
+ const getMonthHeader = () => {
+ let weekDays = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
+ if (options?.startWeekOn?.toUpperCase() === 'SUN') {
+ weekDays[0] = 'Sun'
+ weekDays[6] = 'Mon'
+ }
return weekDays?.map((day, i) => ({
id: `row-day-header-${i+1}`,
flex: 1,
@@ -91,43 +66,56 @@ function Scheduler(props) {
}
/**
- * @name getRows
+ * @name getMonthRows
* @description
* @return {[id: string, day: number, date: date, data: array]}
*/
- const getRows = () => {
+ const getMonthRows = () => {
let rows = [], daysBefore = []
- let iteration = Math.ceil(daysInMonth / 7)
-
- // TODO Rester dans le même mois même si on selectionne
- // une date du mois précédent mais visible sur le calendrier
- let monthStartDate = startOfMonth(selectedDay) // Premier jour du mois
- let monthStartDay = getDay(monthStartDate) // Index du jour de la semaine en chiffre
- let dateDay = parseInt(format(monthStartDate, 'dd')) // Jour de la date du début du mois en chiffre
-
- // If Mon is the first day of week, apply b < monthStartDay
- // and days: (monthStartDay-b)
- for (let b = 1; b <= monthStartDay; b++) {
- let subDate = sub(monthStartDate, {days: (monthStartDay-b) + 1})
- let day = parseInt(format(subDate, 'dd'))
- let data = events.filter((event) => (
- isSameDay(subDate, parse(event?.date, 'yyyy-MM-dd', new Date()))
- ))
+ let iteration = getWeeksInMonth(selectedDay) //Math.ceil(daysInMonth / 7)
+ let startOnSunday = options?.startWeekOn?.toUpperCase() === 'SUN'
+ let monthStartDate = startOfMonth(selectedDay) // First day of month
+ let monthStartDay = getDay(monthStartDate) // Index of the day in week
+ let dateDay = parseInt(format(monthStartDate, 'dd')) // Month start day
+ // Condition check helper
+ const checkCondition = (v) => (startOnSunday ? v <= monthStartDay : v < monthStartDay)
- daysBefore.push({
- id: `day_-${day}`,
- day: day,
- date: subDate,
- data: data
- })
+ if (monthStartDay > 1) {
+ // Add days of precedent month
+ // If Sunday is the first day of week, apply b <= monthStartDay
+ // and days: (monthStartDay-b) + 1
+ for (let i = 1; checkCondition(i); i++) {
+ let subDate = sub(
+ monthStartDate,
+ {days: monthStartDay - i + (startOnSunday ? 1 : 0)}
+ )
+ let day = parseInt(format(subDate, 'dd'))
+ let data = events.filter((event) => (
+ isSameDay(subDate, parse(event?.date, 'yyyy-MM-dd', new Date()))
+ ))
+
+ daysBefore.push({
+ id: `day_-${day}`,
+ day: day,
+ date: subDate,
+ data: data
+ })
+ }
}
- rows.push({ id: 0, days: daysBefore })
+ if (daysBefore?.length > 0) {
+ rows.push({ id: 0, days: daysBefore })
+ }
+
+ // Add days and events data
for (let i = 0; i < iteration; i++) {
let obj = []
-
+
for (
let j = 0;
+ // This condition ensure that days will not exceed 30
+ // i === 0 ? 7 - daysBefore?.length means that we substract inserted days
+ // in the first line to 7
j < (i === 0 ? 7 - daysBefore?.length : 7) && (dateDay <= daysInMonth);
j++
) {
@@ -135,32 +123,154 @@ function Scheduler(props) {
let data = events.filter((event) => (
isSameDay(date, parse(event?.date, 'yyyy-MM-dd', new Date()))
))
-
- obj.push({ id: `day_${dateDay}`, date: date, day: dateDay, data: data })
+
+ obj.push({ id: `day_-${dateDay}`, date: date, day: dateDay, data: data })
dateDay++
}
-
+
if (i === 0 && daysBefore?.length > 0) {
rows[0].days = rows[0].days.concat(obj)
continue
}
- rows.push({id: i, days: obj})
+ if (obj.length > 0) {
+ rows.push({id: i, days: obj})
+ }
}
-
+
+ // Check if last row is not fully filled
+ let lastRow = rows[iteration - 1]
+ let lastRowDaysdiff = 7 - lastRow?.days?.length
+ let lastDaysData = []
+
+ if (lastRowDaysdiff > 0) {
+ let day = lastRow.days[lastRow?.days?.length-1]
+ let addDate = day.date
+
+ for (let i = dateDay; i < (dateDay + lastRowDaysdiff); i++) {
+ addDate = add(addDate, {days: 1})
+ let d = format(addDate, 'dd')
+ // eslint-disable-next-line
+ let data = events.filter((event) => (
+ isSameDay(addDate, parse(event?.date, 'yyyy-MM-dd', new Date()))
+ ))
+ lastDaysData.push({ id: `day_-${d}`, date: addDate, day: d, data: data })
+ }
+ rows[iteration-1].days = rows[iteration-1].days.concat(lastDaysData)
+ }
+
return rows
}
- useEffect(() => {
- if (daysInMonth) {
- setState({rows: getRows(), columns: getHeader()})
+ /**
+ * @name getWeekHeader
+ * @description
+ * @return {{headerClassName: string, headerAlign: string, headerName: string, field: string, flex: number, editable: boolean, id: string, sortable: boolean, align: string}[]}
+ */
+ const getWeekHeader = () => {
+ let data = []
+ let weekStart = startOfWeek(selectedDay, { weekStartsOn: 1 })
+ for (let i = 0; i < 7; i++) {
+ let date = add(weekStart, {days: i})
+ data.push({
+ date: date,
+ weekDay: format(date, 'iii'),
+ day: format(date, 'dd'),
+ month: format(date, 'MM'),
+ })
}
- }, [daysInMonth, selectedDate])
+ return data
+ }
+
+ const getWeekRows = () => {
+ const HOURS = 24 //* 2
+ let data = []
+ let dayStartHour = startOfDay(selectedDay)
+
+ for (let i = 0; i <= HOURS; i++) {
+ let id = `line_${i}`
+ let label = format(dayStartHour, 'HH:mm aaa')
+
+ //TODO Add everyday event capability
+ //if (i === 0) {
+ //id = `line_everyday`; label = 'Everyday'
+ //}
+ //TODO Place the processing bloc here if everyday capability is available
+ // ...
+
+ if (i > 0) {
+ //Start processing bloc
+ let obj = { id: id, label: label, days: [] }
+ let columns = getWeekHeader()
+ // eslint-disable-next-line
+ columns.map((column, index) => {
+ let data = events.filter((event) => {
+ let eventDate = parse(event?.date, 'yyyy-MM-dd', new Date())
+ return (
+ isSameDay(column?.date, eventDate) &&
+ event?.startHour?.toUpperCase() === label?.toUpperCase()
+ )
+ })
+ obj.days.push({
+ id: `column-${index}_m-${column.month}_d-${column.day}_${id}`,
+ date: column?.date,
+ data: data
+ })
+ })
+ // Label affectation
+ data.push(obj) // End processing bloc
+ dayStartHour = add(dayStartHour, {minutes: 60}) // 30
+ }
+ //if (i > 0) {
+ // dayStartHour = add(dayStartHour, {minutes: 30})
+ //}
+ }
+ return data
+ }
+
+ /**
+ * @name handleDateChange
+ * @description
+ * @param day
+ * @param date
+ * @return void
+ */
+ const handleDateChange = (day, date) => {
+ setDaysInMonth(day)
+ setSelectedDay(date)
+ setSelectedDate(format(date, 'MMMM-yyyy'))
+ }
+
+ /**
+ * @name handleModeChange
+ * @description
+ * @param newMode
+ * @return void
+ */
+ const handleModeChange = (newMode) => {
+ setMode(newMode)
+ }
+
+ /**
+ * @name onSearchResult
+ * @description
+ * @param item
+ * @return void
+ */
+ const onSearchResult = (item) => {
+ setSearchResult(item)
+ }
useEffect(() => {
- if (!state?.rows && !state?.columns) {
- setState({rows: getRows(), columns: getHeader()})
+ if (mode) {
+ if (mode === 'month') {
+ setState({columns: getMonthHeader(), rows: getMonthRows()})
+ }
+ if (mode === 'week') {
+ setState({columns: getWeekHeader(), rows: getWeekRows()})
+ }
}
- }, [])
+ // eslint-disable-next-line
+ }, [daysInMonth, selectedDay, selectedDate, mode])
return (
@@ -168,27 +278,47 @@ function Scheduler(props) {
-
- {mode === 'month' &&
- }
-
+ {mode === 'month' &&
+
+
+
+
+ }
+ {mode === 'week' &&
+
+
+
+
+ }
@@ -197,8 +327,13 @@ function Scheduler(props) {
Scheduler.propTypes = {
events: PropTypes.array,
+ options: PropTypes.object,
+ alertProps: PropTypes.object,
+ toolbarProps: PropTypes.object,
+ onEventsChange: PropTypes.func,
onCellClick: PropTypes.func,
- onTaskClick: PropTypes.func
+ onTaskClick: PropTypes.func,
+ onAlertCloseButtonClicked: PropTypes.func,
}
Scheduler.defaultProps = {
diff --git a/src/Toolbar.jsx b/src/Toolbar.jsx
index 1c69c92..2e35993 100644
--- a/src/Toolbar.jsx
+++ b/src/Toolbar.jsx
@@ -9,8 +9,10 @@ import {
} from "@mui/material"
import LocalizationProvider from '@mui/lab/LocalizationProvider'
import StaticDatePicker from '@mui/lab/StaticDatePicker'
+import CloseIcon from '@mui/icons-material/Close'
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'
import ChevronRightIcon from '@mui/icons-material/ChevronRight'
+// eslint-disable-next-line
import MoreVertIcon from '@mui/icons-material/MoreVert'
import TodayIcon from '@mui/icons-material/Today'
import SettingsIcon from '@mui/icons-material/Settings'
@@ -24,17 +26,17 @@ import ToolbarSearchbar from "./ToolbarSeachBar.jsx"
function SchedulerToolbar (props) {
const {
// events data
- events, today, toolbarProps,
+ events, switchMode, today, toolbarProps,
// Mode
onModeChange, onSearchResult,
// Alert props
- openAlert, alertMessage, alertProps,
+ alertProps, onAlertCloseButtonClicked,
// Date
onDateChange
} = props
const [searchResult, setSearchResult] = useState()
- const [mode, setMode] = useState('month')
+ const [mode, setMode] = useState(switchMode)
const [anchorMenuEl, setAnchorMenuEl] = useState(null)
const [anchorDateEl, setAnchorDateEl] = useState(null)
const [selectedDate, setSelectedDate] = useState(today || new Date())
@@ -75,6 +77,7 @@ function SchedulerToolbar (props) {
* @param event
* @return void
*/
+ // eslint-disable-next-line
const handleOpenMenu = (event) => {
setAnchorMenuEl(event.currentTarget)
}
@@ -115,21 +118,25 @@ function SchedulerToolbar (props) {
*/
const handleChangeDate = (method) => {
if (typeof method !== 'function') return
- let newDate = method(selectedDate, {months: 1})
+ let options = mode === 'month' ? {months: 1} : {weeks: 1}
+ let newDate = method(selectedDate, options)
setDaysInMonth(getDaysInMonth(newDate))
setSelectedDate(newDate)
}
useEffect(() => {
if (mode) { onModeChange(mode) }
+ // eslint-disable-next-line
}, [mode])
useEffect(() => {
onDateChange(daysInMonth, selectedDate)
+ // eslint-disable-next-line
}, [daysInMonth, selectedDate])
useEffect(() => {
onSearchResult(searchResult)
+ // eslint-disable-next-line
}, [searchResult])
return (
@@ -145,7 +152,7 @@ function SchedulerToolbar (props) {
handleChangeDate(sub)}
>
@@ -160,10 +167,10 @@ function SchedulerToolbar (props) {
onClick={handleOpenDateSelector}
aria-expanded={openDateSelector ? 'true' : undefined}
>
- {format(selectedDate, 'MMMM-yyyy')}
+ {format(selectedDate, mode === 'month' ? 'MMMM-yyyy' : 'PPP')}
handleChangeDate(add)}
>
@@ -213,21 +220,20 @@ function SchedulerToolbar (props) {
{toolbarProps?.showSwitchModeButtons &&
{ setMode(newMode) }}
>
- {['month', 'Week', 'Day'].map(tb => (
+ {['month', 'week'].map(tb => (
{tb}
))}
}
- {toolbarProps?.showOptions &&
+ {/*toolbarProps?.showOptions &&
- }
+ */}
- {openAlert &&
+ {alertProps?.open &&
-
- {alertMessage}
+
+
+ : null
+ }
+ >
+ {alertProps?.message}
}
@@ -266,20 +287,28 @@ function SchedulerToolbar (props) {
}
SchedulerToolbar.propTypes = {
- title: PropTypes.string,
- openAlert: PropTypes.bool,
- alertMessage: PropTypes.string,
+ today: PropTypes.object.isRequired,
+ events: PropTypes.array.isRequired,
+ switchMode: PropTypes.string.isRequired,
alertProps: PropTypes.object,
- onDateChange: PropTypes.func
+ toolbarProps: PropTypes.object,
+ onDateChange: PropTypes.func.isRequired,
+ onModeChange: PropTypes.func.isRequired,
+ onSearchResult: PropTypes.func.isRequired,
+ onAlertCloseButtonClicked: PropTypes.func.isRequired,
}
SchedulerToolbar.defaultProps = {
- openAlert: false,
- alertMessage: 'This is a scheduler alert',
- alertProps: {color: 'info', severity: 'info'},
+ alertProps: {
+ open: false,
+ message: '',
+ color: 'info',
+ severity: 'info',
+ showActionButton: true,
+ },
toolbarProps: {
showSearchBar: true,
- showSwitchModeButtons: false,
+ showSwitchModeButtons: true,
showDatePicker: true,
showOptions: false
}
diff --git a/src/ToolbarSeachBar.jsx b/src/ToolbarSeachBar.jsx
index 9effb1d..e928dd3 100644
--- a/src/ToolbarSeachBar.jsx
+++ b/src/ToolbarSeachBar.jsx
@@ -3,7 +3,6 @@ import PropTypes from 'prop-types'
import { format, parse } from 'date-fns'
import { styled } from '@mui/material/styles'
import { TextField, Autocomplete, Box } from "@mui/material"
-import { useTheme } from '@mui/material/styles'
const StyledAutoComplete = styled(Autocomplete)(({ theme }) => ({
color: 'inherit',
@@ -17,7 +16,6 @@ const StyledAutoComplete = styled(Autocomplete)(({ theme }) => ({
}))
function ToolbarSearchbar (props) {
- const theme = useTheme()
const {events, onInputChange} = props
const [value, setValue] = useState('')
@@ -31,26 +29,12 @@ function ToolbarSearchbar (props) {
return (
(
-
-
- {option?.title}
-
- )}
+ options={events?.sort((a, b) => -b.groupLabel.localeCompare(a.groupLabel))}
+ groupBy={(option) => option?.groupLabel}
/*
(
@@ -64,13 +48,13 @@ function ToolbarSearchbar (props) {
backgroundColor: option?.color || theme.palette.secondary.main
}}
/>
- {option?.title}
+ {option?.groupLabel}
)
*/
getOptionLabel={(option) => (
option &&
- `${option?.title} | (${option?.startHour} - ${option?.endHour})`
+ `${option?.groupLabel} | (${option?.startHour ?? ''} - ${option?.endHour ?? ''})`
)}
onInputChange={(event, newInputValue) => {
setInputValue(newInputValue)
@@ -78,7 +62,8 @@ function ToolbarSearchbar (props) {
}}
renderOption={(props, option) => (
- {format(parse(option?.date, 'yyyy-MM-dd', new Date()), 'dd-MMMM-yyyy')} ({option?.startHour} - {option?.endHour})
+ {format(parse(option?.date, 'yyyy-MM-dd', new Date()), 'dd-MMMM-yyyy')}
+ ({option?.startHour ?? ''} - {option?.endHour ?? ''})
)}
renderInput={(params) => (
diff --git a/src/WeekModeView.jsx b/src/WeekModeView.jsx
new file mode 100644
index 0000000..e89f84c
--- /dev/null
+++ b/src/WeekModeView.jsx
@@ -0,0 +1,366 @@
+import React, {useState, useEffect} from 'react'
+import PropTypes from 'prop-types'
+import {styled} from "@mui/system"
+import { useTheme } from '@mui/material/styles'
+import {
+ Paper, Typography, Table, TableBody, TableCell, TableContainer,
+ TableHead, TableRow, tableCellClasses, Box,
+} from "@mui/material"
+import { format, parse, add, differenceInMinutes, isValid } from 'date-fns'
+
+const StyledTableCell = styled(TableCell)(({ theme }) => ({
+ [`&.${tableCellClasses.head}`]: {
+ paddingLeft: 4,
+ paddingRight: 4,
+ borderTop: `1px solid #ccc !important`,
+ borderBottom: `1px solid #ccc !important`,
+ borderLeft: `1px solid #ccc !important`,
+ "&:nth-of-type(1)": { borderLeft: `0px !important` }
+ },
+ [`&.${tableCellClasses.body}`]: {
+ fontSize: 12,
+ height: 16,
+ width: 128,
+ maxWidth: 128,
+ cursor: 'pointer',
+ borderLeft: `1px solid #ccc`,
+ "&:nth-of-type(1)": {
+ width: 80,
+ maxWidth: 80,
+ },
+ "&:nth-of-type(8n+1)": { borderLeft: 0 },
+ "&:nth-of-type(even)": {
+ //backgroundColor: theme.palette.action.hover
+ },
+ },
+ [`&.${tableCellClasses.body}:hover`]: {
+ backgroundColor: "#eee"
+ }
+}))
+
+const StyledTableRow = styled(TableRow)(({ theme }) => ({
+ '&:nth-of-type(odd)': {
+ //backgroundColor: theme.palette.action.hover,
+ },
+ // hide last border
+ '&:last-child td, &:last-child th': {
+ border: 0
+ }
+}))
+
+const StyledTableContainer = styled(TableContainer)(({ theme }) => ({
+ "&::-webkit-scrollbar": {
+ width: 7,
+ height: 6
+ },
+ "&::-webkit-scrollbar-track": {
+ WebkitBoxShadow: "inset 0 0 6px rgb(125, 161, 196, 0.5)"
+ },
+ "&::-webkit-scrollbar-thumb": {
+ WebkitBorderRadius: 4,
+ borderRadius: 4,
+ background: "rgba(0, 172, 193, .5)",
+ WebkitBoxShadow: "inset 0 0 6px rgba(25, 118, 210, .5)"
+ },
+ "&::-webkit-scrollbar-thumb:window-inactive": {
+ background: "rgba(125, 161, 196, 0.5)"
+ }
+}))
+
+function WeekModeView (props) {
+ const {
+ columns, rows, searchResult, onTaskClick, onCellClick, onEventsChange
+ } = props
+ const theme = useTheme()
+ const [state, setState] = useState({columns, rows})
+
+ /**
+ * @name onCellDragOver
+ * @param e
+ * @return void
+ */
+ const onCellDragOver = (e) => {
+ e.preventDefault()
+ }
+
+ /**
+ * @name onCellDragStart
+ * @description
+ * @param e
+ * @param item
+ * @param rowLabel
+ * @param rowIndex
+ * @param dayIndex
+ * @return void
+ */
+ const onCellDragStart = (e, item, rowLabel, rowIndex, dayIndex) => {
+ setState({
+ ...state,
+ itemTransfert: {item, rowLabel, rowIndex, dayIndex}}
+ )
+ }
+
+ /**
+ * @name onCellDragEnter
+ * @description
+ * @param e
+ * @param rowLabel
+ * @param rowIndex
+ * @param dayIndex
+ * @return void
+ */
+ const onCellDragEnter = (e, rowLabel, rowIndex, dayIndex) => {
+ e.preventDefault()
+ setState({...state, transfertTarget: {rowLabel, rowIndex, dayIndex}})
+ }
+
+ /**
+ * @name onCellDragEnd
+ * @description
+ * @param e
+ * @return void
+ */
+ const onCellDragEnd = (e) => {
+ e.preventDefault()
+ if (!state?.itemTransfert || !state?.transfertTarget) {
+ return //console.log('undefined source or target')
+ }
+
+ let transfert = state.itemTransfert
+ let transfertTarget = state.transfertTarget
+ let rowsData = Array.from(rows)
+ let day = rowsData[transfertTarget?.rowIndex]?.days[transfertTarget?.dayIndex]
+
+ if (day) {
+ let foundEventIndex = day.data.findIndex(e =>
+ e.id === transfert.item.id &&
+ e.startHour === transfert?.item?.startHour &&
+ e.endHour === transfert?.item?.endHour
+ )
+ // Task already exists in the data array of the chosen cell
+ if (foundEventIndex !== -1) {
+ return
+ }
+
+ // Timeline label (00:00 am, 01:00 am, etc.)
+ let label = transfertTarget.rowLabel?.toUpperCase()
+ // Event cell item to transfert
+ let prevEventCell = rowsData[transfert?.rowIndex].days[transfert?.dayIndex]
+ // Event's end hour
+ let endHourDate = parse(transfert.item.endHour, 'p', day?.date)
+ // Event start hour
+ let startHourDate = parse(transfert.item.startHour, 'p', day?.date)
+ // Minutes difference between end and start event hours
+ let minutesDiff = differenceInMinutes(endHourDate, startHourDate)
+ // New event end hour according to it new cell
+ let newEndHour = add(
+ parse(label, 'p', day?.date), {minutes: minutesDiff}
+ )
+
+ // If event is moved from timeline 00:00 AM
+ if (label === '00:00 AM') {
+ minutesDiff = differenceInMinutes(endHourDate, startHourDate)
+ newEndHour = add(day?.date, {minutes: minutesDiff})
+ }
+
+ // If event is moved from timeline 01:00 AM
+ if (label === '01:00 AM') {
+ minutesDiff = differenceInMinutes(endHourDate, startHourDate)
+ newEndHour = add(parse(label, 'p', day?.date), {minutes: minutesDiff})
+
+ if (!isValid(startHourDate)){
+ startHourDate = day?.date
+ minutesDiff = differenceInMinutes(endHourDate, startHourDate)
+ newEndHour = add(
+ parse(label, 'p', startHourDate), {minutes: minutesDiff}
+ )
+ }
+ }
+
+ // If the start date of event is invalid, it's probably cause by date-fns
+ // So we initialize it at 00:00 AM of the event day
+ if (!isValid(startHourDate)){
+ startHourDate = day?.date
+ minutesDiff = differenceInMinutes(endHourDate, startHourDate)
+ newEndHour = add(day?.date, {minutes: minutesDiff})
+
+ if (label !== '00:00 AM') {
+ newEndHour = add(
+ parse(label, 'p', startHourDate), {minutes: minutesDiff}
+ )
+ }
+ }
+
+ prevEventCell?.data?.splice(transfert?.item?.itemIndex, 1)
+ transfert.item.startHour = label
+ transfert.item.endHour = format(newEndHour, 'HH:mm aaa')
+ transfert.item.date = format(day?.date, 'yyyy-MM-dd')
+ day.data.push(transfert.item)
+ setState({...state, rows: rowsData})
+ }
+ }
+
+ /**
+ * @name handleCellClick
+ * @description
+ * @param event
+ * @param row
+ * @param day
+ * @return void
+ */
+ const handleCellClick = (event, row, day) => {
+ console.log(day)
+ event.preventDefault()
+ event.stopPropagation()
+ //setState({...state, activeItem: day})
+ onCellClick(event, row, day)
+ }
+
+ /**
+ * @name renderTask
+ * @description
+ * @param tasks
+ * @param rowLabel
+ * @param rowIndex
+ * @param dayIndex
+ * @return {unknown[] | undefined}
+ */
+ const renderTask = (tasks, rowLabel, rowIndex, dayIndex) => {
+ return tasks?.map((task, itemIndex) => (
+ (
+ (
+ searchResult &&
+ (task?.groupLabel === searchResult?.groupLabel || task?.user === searchResult?.user)
+ ) || !searchResult
+ ) &&
+ (
+ handleTaskClick(e, task)}
+ key={`item_id-${itemIndex}_r-${rowIndex}_d-${dayIndex}`}
+ onDragStart={e => onCellDragStart(
+ e, {...task, itemIndex}, rowLabel, rowIndex, dayIndex
+ )}
+ sx={{
+ py: 0, mb: .5, color: "#fff",
+ backgroundColor: task?.color || theme.palette.primary.light
+ }}
+ >
+
+ {task?.label}
+
+
+ )
+ ))
+ }
+
+ /**
+ * @name handleTaskClick
+ * @description
+ * @param event
+ * @param task
+ * @return void
+ */
+ const handleTaskClick = (event, task) => {
+ event.preventDefault()
+ event.stopPropagation()
+ onTaskClick(event, task)
+ }
+
+ useEffect(() => {
+ if (state?.rows && state?.itemTransfert?.item) {
+ onEventsChange(state?.itemTransfert?.item)
+ }
+ // eslint-disable-next-line
+ }, [state?.rows, state?.itemTransfert?.item])
+
+ return (
+
+
+
+
+
+ {
+ columns?.map((column, index) => (
+
+ {column?.weekDay} {column?.month}/{column?.day}
+
+ ))
+ }
+
+
+
+ {
+ rows?.map((row, rowIndex) => (
+
+ handleCellClick(event, row)}
+ >
+ {row?.label}
+ {row?.data?.length > 0 && renderTask(row?.data, row.id)}
+
+ {row?.days?.map((day, dayIndex) => {
+ return (
+ onCellDragEnter(e, row?.label, rowIndex, dayIndex)}
+ onClick={(event) => handleCellClick(
+ event, {rowIndex, ...row}, {dayIndex, ...day}
+ )}
+ >
+ {day?.data?.length > 0 && renderTask(day?.data, row?.label, rowIndex, dayIndex)}
+
+ )
+ })}
+
+ ))
+ }
+
+
+
+ )
+}
+
+WeekModeView.propTypes = {
+ events: PropTypes.array,
+ columns: PropTypes.array,
+ rows: PropTypes.array,
+ date: PropTypes.string,
+ searchResult: PropTypes.object,
+ onDateChange: PropTypes.func.isRequired,
+ onTaskClick: PropTypes.func.isRequired,
+ onCellClick: PropTypes.func.isRequired,
+ onEventsChange: PropTypes.func.isRequired
+}
+
+WeekModeView.defaultProps = {
+
+}
+
+export default WeekModeView
\ No newline at end of file