@diagramers/admin
Version:
Diagramers Admin Template - React starter for admin dashboards.
280 lines (261 loc) • 9.51 kB
JavaScript
import React, { useState, useRef, useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { Row, Col, Card, Button, Dropdown } from 'react-bootstrap';
import HtmlHead from 'components/html-head/HtmlHead';
import BreadcrumbList from 'components/breadcrumb-list/BreadcrumbList';
import CsLineIcons from 'cs-line-icons/CsLineIcons';
import FullCalendar from '@fullcalendar/react';
import dayGridPlugin from '@fullcalendar/daygrid';
import timeGridPlugin from '@fullcalendar/timegrid';
import interactionPlugin from '@fullcalendar/interaction';
import bootstrapPlugin from '@fullcalendar/bootstrap';
import ModalAddEdit from './components/ModalAddEdit';
import { getEvents, setSelectedEvent, updateEvent } from './calendarSlice';
const CustomToggle = React.forwardRef(({ onClick }, ref) => (
<Button
ref={ref}
size="sm"
variant="foreground"
className="btn-icon btn-icon-only shadow align-top mt-n2"
onClick={(e) => {
e.preventDefault();
onClick(e);
}}
>
<CsLineIcons icon="more-horizontal" data-cs-size="15" />
</Button>
));
const colorsMap = [
{ color: 'primary', category: 'Work' },
{ color: 'secondary', category: 'Education' },
{ color: 'tertiary', category: 'Personal' },
];
const CalendarApp = () => {
const htmlTitle = 'Calendar';
const htmlDescription = 'Implementation for a basic events and schedule application that built on top of Full Calendar plugin.';
const breadcrumbs = [
{ to: '', text: 'Home' },
{ to: 'apps', title: 'Apps' },
];
const calendarRef = useRef(null);
const dispatch = useDispatch();
const { events: eventsNoColors } = useSelector((state) => state.calendar);
const { themeValues } = useSelector((state) => state.settings);
const [events, setEvents] = useState('');
const [dateTitle, setDateTitle] = useState('');
const [selectedView, setSelectedView] = useState('dayGridMonth');
const [isShowModalAddEdit, setIsShowModalAddEdit] = useState(false);
useEffect(() => {
const coloredEvents = eventsNoColors.map((event) => {
const coloredEvent = { ...event };
if (event.category) {
const foundColor = colorsMap.find((x) => x.category === event.category);
if (foundColor) {
coloredEvent.color = themeValues[foundColor.color];
}
}
return coloredEvent;
});
setEvents(coloredEvents);
return () => {};
}, [eventsNoColors, themeValues]);
const onPrevButtonClick = () => {
const calendarApi = calendarRef.current.getApi();
calendarApi.prev();
setDateTitle(calendarApi.view.title);
};
const onNextButtonClick = () => {
const calendarApi = calendarRef.current.getApi();
calendarApi.next();
setDateTitle(calendarApi.view.title);
};
const onNewEventClick = () => {
try {
dispatch(setSelectedEvent({ id: 0, title: 'New Event', start: '', end: '' }));
setIsShowModalAddEdit(true);
} catch (e) {
console.log('This action could not be completed');
}
};
const viewDidMount = ({ view }) => {
setDateTitle(view.title);
};
const changeView = (view) => {
setSelectedView(view);
if (calendarRef.current) {
calendarRef.current.getApi().changeView(view);
}
};
const getToday = () => {
if (calendarRef.current) {
calendarRef.current.getApi().today();
}
};
// handlers for user actions
// ------------------------------------------------------------------------------------------
const handleDateSelect = (selectInfo) => {
const calendarApi = selectInfo.view.calendar;
calendarApi.unselect(); // clear date selection
try {
dispatch(setSelectedEvent({ id: 0, title: 'New Event', start: selectInfo.startStr, end: selectInfo.endStr }));
setIsShowModalAddEdit(true);
} catch (e) {
console.log('This action could not be completed');
}
};
const handleEventClick = (clickInfo) => {
const { id, url } = clickInfo.event;
if (!url) {
dispatch(setSelectedEvent(events.find((x) => x.id === id)));
setIsShowModalAddEdit(true);
}
};
// handlers that initiate reads/writes via the 'action' props
// ------------------------------------------------------------------------------------------
const handleDates = (rangeInfo) => {
try {
dispatch(getEvents(rangeInfo.startStr, rangeInfo.endStr));
} catch (e) {
console.log('This action could not be completed');
}
};
const handleEventChange = (changeInfo) => {
try {
const event = changeInfo.event.toPlainObject();
const { id, start, title, end, extendedProps } = event;
dispatch(updateEvent({ id, start, end, title, category: extendedProps.category, color: extendedProps.color }));
} catch (e) {
console.log('This action could not be completed');
changeInfo.revert();
}
};
const renderEventContent = (eventInfo) => {
const { timeText, backgroundColor, borderColor } = eventInfo;
const { allDay, title } = eventInfo.event;
if (!allDay) {
return (
<>
<div className="fc-daygrid-event-dot" style={{ backgroundColor, borderColor }} />
<div className="fc-event-time">{timeText}</div>
<div className="fc-event-title">{title}</div>
</>
);
}
return (
<>
<div className="fc-event-main-frame">
<div className="fc-event-title-container">
<div className="fc-event-title fc-sticky">{title}</div>
</div>
</div>
</>
);
};
return (
<>
<HtmlHead title={htmlTitle} description={htmlDescription} />
{/* Title Start */}
<div className="page-title-container">
<Row className="g-0">
<Col xs="auto" className="mb-2 mb-md-0 me-auto">
<div className="w-auto sw-md-30">
<h1 className="mb-0 pb-0 display-4">{htmlTitle}</h1>
<BreadcrumbList items={breadcrumbs} />
</div>
</Col>
<div className="w-100 d-md-none" />
<Col xs="auto" className="d-flex align-items-start justify-content-end">
<Button variant="outline-primary" className="btn-icon btn-icon-only ms-1" onClick={onPrevButtonClick}>
<CsLineIcons icon="chevron-left" />
</Button>
<Button variant="outline-primary" className="btn-icon btn-icon-only ms-1" onClick={onNextButtonClick}>
<CsLineIcons icon="chevron-right" />
</Button>
</Col>
<Col md="auto" className="d-flex align-items-start justify-content-end">
<Button variant="outline-primary" className="btn-icon btn-icon-start ms-1 w-100 w-md-auto" onClick={onNewEventClick}>
<CsLineIcons icon="plus" /> <span>Add Event</span>
</Button>
</Col>
</Row>
</div>
{/* Title End */}
{/* Calendar Title Start */}
<div className="d-flex justify-content-between">
<h2 className="small-title">{dateTitle}</h2>
<Dropdown>
<Dropdown.Toggle as={CustomToggle} id="dropdown-custom-components" />
<Dropdown.Menu
className="super-colors shadow dropdown-menu-end"
popperConfig={{
modifiers: [
{
name: 'offset',
options: {
offset: [0, 5],
},
},
{
name: 'computeStyles',
options: {
gpuAcceleration: false,
},
},
],
}}
>
<Dropdown.Item eventKey="dayGridMonth" active={selectedView === 'dayGridMonth'} onClick={() => changeView('dayGridMonth')}>
Month
</Dropdown.Item>
<Dropdown.Item eventKey="timeGridWeek" active={selectedView === 'timeGridWeek'} onClick={() => changeView('timeGridWeek')}>
Week
</Dropdown.Item>
<Dropdown.Item eventKey="timeGridDay" active={selectedView === 'timeGridDay'} onClick={() => changeView('timeGridDay')}>
Day
</Dropdown.Item>
<Dropdown.Divider />
<Dropdown.Item eventKey="today" onClick={getToday}>
Today
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
</div>
{/* Calendar Title End */}
<Card body>
<FullCalendar
ref={calendarRef}
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin, bootstrapPlugin]}
headerToolbar={false}
initialView="dayGridMonth"
themeSystem="bootstrap"
editable
selectable
selectMirror
dayMaxEvents
weekends
datesSet={handleDates}
select={handleDateSelect}
events={events}
eventContent={renderEventContent} // custom render function
eventClick={handleEventClick}
eventChange={handleEventChange} // called for drag-n-drop/resize
viewDidMount={viewDidMount}
eventTimeFormat={{
hour: '2-digit',
minute: '2-digit',
meridiem: false,
}}
/>
</Card>
{isShowModalAddEdit && (
<ModalAddEdit
show={isShowModalAddEdit}
onHide={() => {
setIsShowModalAddEdit(false);
}}
/>
)}
</>
);
};
export default CalendarApp;