Skip to content

Commit

Permalink
Calendar new views (#57)
Browse files Browse the repository at this point in the history
* touch-up month-view weekday-headers

the month view has a scroll-bar, the header needs some right-margin.

the 15px is taken from the week- and day-view which has a scroll-bar and
that right-margin for the header (div.rbc-time-header style attribute).

* dnd: update week-wrapper

updating WeekWrapper.js to the upstream version of Nov, 6th 2020. [1]

fixes crashes on drop into all-day in the week view of the calendar.

this is the upstream version, just with a different semicolon-style and
one typo fix.

[1]: https://github.com/jquense/react-big-calendar/blob/34aec3a64d018ec0f9dce780a6d4eeb03692c9fd/src/addons/dragAndDrop/WeekWrapper.js

* dnd: fix drop on time-gutter

previously dropping on the time-gutter leaves calendar in drag state.

fix is to add the ability of specific drop targets and cancel/reset drag-
and-drop if the drop was not on an allowed drop target.

streamlined with [1].

[1]: jquense/react-big-calendar#1901

* add week, day and agenda view

add three of the rbc views to the calendar:

1. week; 2. day; 3. agenda

- row details by single-click on week and day events
- background color for day/week view (non-full-day events)
- css: reset day-event border
- css: bootstrap needs button style reset (time-header)
- css: center agenda no events text

* show week-of-year

show the week-number in the top-left corner of the timer-gutter.

display format is weekOfYearFormat (localizer), which is decorated as the
default moment localizer does not have it.

* all-/cross- day event

basically, given the end-date is undefined|null|"", the event is all-day.

additionally an event is displayed in the all-day slot if it is across
multiple days.

previously there was no need to handle all- or cross-days to this extend
for any calendar event as only the original week view was in use and
additionally the year view.

now this is needed as the week and day views are active and they display
event with time(s) and within the all-/cross-day slot on top.

this is handling events with a bogus end-date gracefully:

in case an end-date is before the start-date, the start-date is leading
and the end-date of the event will be the events start-date plus a fixed
amount of time (e.g. one hour). this is useful for events with date and
time (with minute precision).

and an offset of 12 hours is necessary to display the event on the
correct day within all views, incl. the year view.

* all-/cross- day: drag-and-drop

with the changes to the event model and the new views, dragging and
dropping events have more features now, like turning an event into an
all-day event.

row updates are done more specifically to the columns that actually need
changes.

the events collection is updated specifically, too. previously it was
not updated and re-created after row changes did propagate.

when there are full-day-dates due to start date without minute precision
(all day events), and the end date is also w/o minute precision, then
it needs to be noon (12:00) not start of day (00:00) as otherwise the
day would not count as end (the year view seems most picky).

it is only necessary to modify the end-date to make it into the all-day
slot if it is on the same day.

* create new events by selecting

creating on the same-day slot a time-range creates a new event.

if the events start date can not be edited (e.g. formula), the selection
is prevented (handleSelecting() implementation).

NOTE: intentionally new events are without a title (cdb, rdb)

* minor fix

redundant variable

* update code:
1. adjust some styles;
2. update translation;
3. fix move event if the end date comes from the formula column;
4. check whether the container in the WeekWrapper is valid.

Co-authored-by: renjie-run <rj.aiyayao@gmail.com>
  • Loading branch information
ktomk and renjie-run authored May 21, 2021
1 parent c2f018c commit 6193dac
Show file tree
Hide file tree
Showing 20 changed files with 336 additions and 105 deletions.
8 changes: 4 additions & 4 deletions public/locale/de/calendar.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
"Color_From": "Farbe",
"Unnamed_record": "Neuer Eintrag",
"more": "mehr",
".rbc.localizer.moment.dateFormat": "DD",
".rbc.localizer.moment.dayFormat": "DD ddd",
".rbc.localizer.moment.dateFormat": "DD.",
".rbc.localizer.moment.dayFormat": "ddd, DD.",
".rbc.localizer.moment.weekdayFormat": "ddd",
".rbc.localizer.moment.monthFormat": "MMMM",
".rbc.localizer.moment.yearHeaderFormat": "YYYY",
".rbc.localizer.moment.dayHeaderFormat": "dddd, DD. MMMM",
".rbc.localizer.moment.agendaDateFormat": "ddd MMM DD",
".rbc.localizer.moment.agendaDateFormat": "ddd, DD. MMMM",
".rbc.localizer.moment.yearMonthWeekdayFormat": "ddd",
".rbc.localizer.moment.yearMonthDateFormat": "DD",
".rbc.localizer.moment.yearMonthDateFormat": "D",
".rbc.localizer.moment.weekOfYearFormat": "WW",
".rbc.messages.date": "Datum",
".rbc.messages.time": "Zeit",
Expand Down
2 changes: 1 addition & 1 deletion public/locale/en/calendar.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
".rbc.localizer.moment.dayHeaderFormat": "MM-dd-DD",
".rbc.localizer.moment.agendaDateFormat": "ddd MMM DD",
".rbc.localizer.moment.yearMonthWeekdayFormat": "dd",
".rbc.localizer.moment.yearMonthDateFormat": "DD",
".rbc.localizer.moment.yearMonthDateFormat": "D",
".rbc.localizer.moment.weekOfYearFormat": "WW",
".rbc.messages.date": "Date",
".rbc.messages.time": "Time",
Expand Down
2 changes: 1 addition & 1 deletion public/locale/fr/calendar.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
".rbc.localizer.moment.dayHeaderFormat": "ddd MMM DD",
".rbc.localizer.moment.agendaDateFormat": "ddd MMM DD",
".rbc.localizer.moment.yearMonthWeekdayFormat": "dd",
".rbc.localizer.moment.yearMonthDateFormat": "DD",
".rbc.localizer.moment.yearMonthDateFormat": "D",
".rbc.localizer.moment.weekOfYearFormat": "WW",
".rbc.messages.date": "Date",
".rbc.messages.time": "Heure",
Expand Down
52 changes: 52 additions & 0 deletions public/locale/ru/calendar.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"Calendar": "Календарь",
"Submit": "Сохранить",
"Cancel": "Отмена",
"Start_Date": "Поле дата начала",
"End_Date_Optional": "Поле дата окончания (необязательно)",
"Color_From": "Цвет поля",
"Unnamed_record": "Нет имени",
"more": "больше",
".rbc.localizer.moment.dateFormat": "DD",
".rbc.localizer.moment.dayFormat": "DD ddd",
".rbc.localizer.moment.weekdayFormat": "ддд",
".rbc.localizer.moment.monthFormat": "МММ",
".rbc.localizer.moment.yearHeaderFormat": "ГГГГ",
".rbc.localizer.moment.dayHeaderFormat": "ММ-дд-ДД",
".rbc.localizer.moment.agendaDateFormat": "ddd MMM DD",
".rbc.localizer.moment.yearMonthWeekdayFormat": "дд",
".rbc.localizer.moment.yearMonthDateFormat": "D",
".rbc.localizer.moment.weekOfYearFormat": "WW",
".rbc.messages.date": "Дата",
".rbc.messages.time": "Время",
".rbc.messages.event": "Событие",
".rbc.messages.allDay": "Весь день",
".rbc.messages.week": "Неделя",
".rbc.messages.work_week": "Рабочая неделя",
".rbc.messages.day": "День",
".rbc.messages.month": "Месяц",
".rbc.messages.previous": "Назад",
".rbc.messages.next": "Вперед",
".rbc.messages.yesterday": "Вчера",
".rbc.messages.tomorrow": "завтрашний день",
".rbc.messages.today": "Сегодня",
".rbc.messages.agenda": "Повестка дня",
".rbc.messages.year": "Год",
".rbc.messages.noEventsInRange": "В этом диапазоне событий нет.",
"No_date_field_to_place_records_on_the_calendar": "Нет поля с датой для размещения записей в календаре.",
"There_are_no_records": "Запись отсутствует.",
"Default_View": "Вид по умолчанию",
"New_View": "Новый Вид",
"Rename_View": "Переименовать Вид",
"Delete_View": "Удалить Вид",
"Name": "Имя",
"Name_is_required": "Имя обязательно",
"Settings": "Настройки",
"Table": "Таблица",
"View": "Вид",
"Title": "Поле заголовка",
"No_options": "Нет опций",
"Not_used": "Не используется",
"Choose_the_time_range": "Выберите временной диапазон",
"Out_of_range": "Можно выбрать не более 12 месяцев."
}
22 changes: 11 additions & 11 deletions public/locale/zh_CN/calendar.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,24 @@
".rbc.localizer.moment.dayHeaderFormat": "MMMDD日 星期dd",
".rbc.localizer.moment.agendaDateFormat": "ddd MMM DD",
".rbc.localizer.moment.yearMonthWeekdayFormat": "dd",
".rbc.localizer.moment.yearMonthDateFormat": "DD",
".rbc.localizer.moment.yearMonthDateFormat": "D",
".rbc.localizer.moment.weekOfYearFormat": "WW",
".rbc.messages.date": "Date",
".rbc.messages.time": "Time",
".rbc.messages.event": "Event",
".rbc.messages.allDay": "All Day",
".rbc.messages.date": "日期",
".rbc.messages.time": "时间",
".rbc.messages.event": "事件",
".rbc.messages.allDay": "全天",
".rbc.messages.week": "",
".rbc.messages.work_week": "Work Week",
".rbc.messages.work_week": "工作周",
".rbc.messages.day": "",
".rbc.messages.month": "",
".rbc.messages.previous": "Back",
".rbc.messages.next": "Next",
".rbc.messages.yesterday": "Yesterday",
".rbc.messages.tomorrow": "Tomorrow",
".rbc.messages.previous": "返回",
".rbc.messages.next": "下一步",
".rbc.messages.yesterday": "昨天",
".rbc.messages.tomorrow": "明天",
".rbc.messages.today": "今天",
".rbc.messages.agenda": "议程",
".rbc.messages.year": "",
".rbc.messages.noEventsInRange": "There are no events in this range.",
".rbc.messages.noEventsInRange": "此范围内没有事件。",
"No_date_field_to_place_records_on_the_calendar": "没有用于将记录放置在日历上的日期字段。",
"There_are_no_records": "没有记录。",
"Default_View": "默认视图",
Expand Down
12 changes: 12 additions & 0 deletions src/Calendar.js
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,18 @@ class Calendar extends React.Component {
}) {
let names = viewNames(views);
const msgs = message(messages);

components[CALENDAR_VIEWS.WEEK] = {
timeGutterHeader: (props) => {
const { localizer, range } = props;
return (
<div className={'rbc-week-of-year'}>
{localizer.format(range[1], 'weekOfYearFormat')}
</div>
);
}
};

return {
viewNames: names,
localizer: mergeWithDefaults(localizer, culture, formats, msgs),
Expand Down
99 changes: 88 additions & 11 deletions src/ReactBigCalendar.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const propTypes = {
getRowById: PropTypes.func,
};

const calendarViews = [CALENDAR_VIEWS.YEAR, CALENDAR_VIEWS.MONTH];
const calendarViews = [CALENDAR_VIEWS.YEAR, CALENDAR_VIEWS.MONTH, CALENDAR_VIEWS.WEEK, CALENDAR_VIEWS.DAY, CALENDAR_VIEWS.AGENDA];
const localizer = momentLocalizer(moment);

class ReactBigCalendar extends React.Component {
Expand Down Expand Up @@ -160,15 +160,14 @@ class ReactBigCalendar extends React.Component {
const { key: endDateColumnKey, name: endDateColumnName, type: endDateColumnType } = endDateColumn || {};
const title = this.getEventTitle(rawRow, titleColumnType, titleColumnName);
const date = startDateColumnType === 'formula' ? rawRow[startDateColumnName] : row[startDateColumnKey];
// start date must exist and valid.
// start date must exist and be valid.
if (!date || !isValidDateObject(new Date(date))) {
return null;
}
const endDate = this.getEventEndDate(rawRow, row, endDateColumnType, endDateColumnName, endDateColumnKey, date);
// Invalid event:
// 1. invalid end date
// 2. duration less than 0 between end date with start date.
if (endDate && (!isValidDateObject(new Date(endDate)) || moment(endDate).isBefore(date))) {
// end date if exists must be valid
// NOTE: end date might be before start date, this is delegated to TableEvent()
if (endDate && !isValidDateObject(new Date(endDate))) {
return null;
}
const eventColors = TableEvent.getColors({row, colorColumn, optionColors, highlightColors});
Expand All @@ -179,6 +178,8 @@ class ReactBigCalendar extends React.Component {
this.props.onRowExpand(row, this.props.activeTable);
}

onSelectEvent = ({row}) => this.onRowExpand(row);

onInsertRow = (rowData) => {
let { activeTable, activeView, onInsertRow, rows } = this.props;
let row_id = rows.length > 0 ? rows[rows.length - 1]._id : '';
Expand All @@ -190,10 +191,47 @@ class ReactBigCalendar extends React.Component {
return moment(date).format(targetFormat);
}

/**
* create new event row
*
* @param {Date} start
* @param {Date} end
*/
createEvent = ({ start, end }) => {
const { CellType, activeTable, appendRow, setting: {settings} } = this.props;
const startDateColumnName = settings[SETTING_KEY.COLUMN_START_DATE];
const endDateColumnName = settings[SETTING_KEY.COLUMN_END_DATE];
const startDateColumn = this.getDateColumn(startDateColumnName);
const endDateColumn = endDateColumnName ? this.getDateColumn(endDateColumnName) : null;
if (startDateColumn.type !== CellType.DATE) {
return;
}
const rowData = {};
const startDateFormat = startDateColumn.data && startDateColumn.data.format;
const startDateMinutePrecision = startDateFormat && startDateFormat.indexOf('HH:mm') > -1;
rowData[startDateColumn.name] = this.getFormattedDate(start, startDateFormat);
if (startDateMinutePrecision) {
switch (endDateColumn.type) {
case CellType.DATE:
if (endDateColumn !== startDateColumn) {
rowData[endDateColumn.name] = this.getFormattedDate(end, endDateColumn.data && endDateColumn.data.format);
}
break;
case CellType.DURATION:
rowData[endDateColumn.name] = (Math.abs(end - start) / 1000).toFixed();
break;
default:
break;
}
}
appendRow(activeTable, rowData);
}

moveEvent = ({ event, start, end, isAllDay: droppedOnAllDaySlot }) => {
const { events } = this.state;
const idx = events.indexOf(event);
let updatedData = {};
let { activeTable, modifyRow, setting, CellType } = this.props;
const { settings = {} } = setting;
let { activeTable, modifyRow, setting: {settings}, CellType, getRowById} = this.props;
const startDateColumnName = settings[SETTING_KEY.COLUMN_START_DATE];
const endDateColumnName = settings[SETTING_KEY.COLUMN_END_DATE];
let startDateColumn = this.getDateColumn(startDateColumnName);
Expand All @@ -204,19 +242,55 @@ class ReactBigCalendar extends React.Component {
return;
}
const startDateFormat = data && data.format;
const startDateMinutePrecision = startDateFormat && startDateFormat.indexOf('HH:mm') > -1;
updatedData[startDateColumn.name] = this.getFormattedDate(start, startDateFormat);
if (!droppedOnAllDaySlot && startDateMinutePrecision && event.allDay) {
event.allDay = false;
end = new Date(start.valueOf());
end.setHours(end.getHours() + TableEvent.FIXED_PERIOD_OF_TIME_IN_HOURS);
}
if (droppedOnAllDaySlot && startDateMinutePrecision) {
// an event can only be made all-day if it has a true end-date field when its start-date is with
// time (with minute precision) [if an event is across two days, it is also displayed on top]
if (endDateColumn && (endDateColumn !== startDateColumn) && endDateColumn.type === CellType.DATE) {
const startEndSameDay = moment(start).format('YYYY-MM-DD') === moment(end).format('YYYY-MM-DD');
if (startEndSameDay) {
end = moment(end).add(1, 'day').startOf('day').toDate();
}
}
}
}
if (endDateColumn) {
const { type, data } = endDateColumn;
if (type === CellType.FORMULA) {
return;
end = event.end; // the end date get from the formula column is read only.
}
if (type !== CellType.DURATION) {
if (type === CellType.DATE) {
const endDateFormat = data && data.format;
updatedData[endDateColumn.name] = this.getFormattedDate(end, endDateFormat);
}
}
modifyRow(activeTable, event.row, updatedData);

const updatedEvent = { ...event, start, end, ...{row: getRowById(activeTable, event.row._id)}};
const nextEvents = [...events];
nextEvents.splice(idx, 1, updatedEvent);
this.setState({
events: nextEvents
});
}

handleSelectSlot = ({ action, start, end}) => {
if (action === 'select') {
this.createEvent({start, end});
}
}

handleSelecting = ({ start, end }) => {
const { CellType, setting: {settings} } = this.props;
const startDateColumnName = settings[SETTING_KEY.COLUMN_START_DATE];
const startDateColumn = this.getDateColumn(startDateColumnName);
return startDateColumn.type === CellType.DATE;
}

render() {
Expand All @@ -235,9 +309,12 @@ class ReactBigCalendar extends React.Component {
onSelectView={this.onSelectView}
defaultDate={new Date()}
onRowExpand={this.onRowExpand}
onSelectEvent={this.onSelectEvent}
onInsertRow={this.onInsertRow}
hideViewSettingPanel={this.props.hideViewSettingPanel}
selectable={true}
selectable
onSelectSlot={this.handleSelectSlot}
onSelecting={this.handleSelecting}
onEventDrop={this.moveEvent}
isExporting={this.props.isExporting}
exportedMonths={this.props.exportedMonths}
Expand Down
Loading

0 comments on commit 6193dac

Please sign in to comment.