react-native-week-view icon indicating copy to clipboard operation
react-native-week-view copied to clipboard

Is it possible to change the background color of specific time blocks?

Open jeminsieow opened this issue 4 years ago • 10 comments

image

I'm using this library to create a group calendar and it's pretty amazing so far!

Is it possible to change the background color of specific blocks of time? ie. from an array of startDate and endDate objects similar to events. The use case would be to show when the group is free (the green cells). These cells should still be pressable for the onGridClick callback. I have attached a simple mockup. Could you add this in the future for me?

jeminsieow avatar May 10 '21 14:05 jeminsieow

Sounds interesting! Thanks for the detailed description and image, is very helpful

I think you can already achieve this by: setting an empty description, setting colors with a low opacity, and using the onEventPress callback:

const myEvents = [
  {
    id: 1,
    description: '', // Empty, so it looks like an empty timeslot
    startDate: new Date(2021, 4, 12, 13), // Specify the timeslot
    endDate: new Date(2021, 4, 12, 14),
    color: 'rgba(0,255,0,0.2)', // Use a green with low opacity (or any other color, of course)
    isTimeslot: true, // Used in the callback later
  },
  {
    id: 2,
    description: '',
    startDate: new Date(2021, 4, 12, 14),
    endDate: new Date(2021, 4, 12, 15),
    color: 'rgba(0,255,0,0.2)',
    isTimeslot: true,
  },
  {
    id: 3,
    description: '',
    startDate: new Date(2021, 4, 12, 15),
    endDate: new Date(2021, 4, 12, 16),
    color: 'rgba(0,255,0,0.2)',
    isTimeslot: true,
  },
  {
    id: 4,
    description: '',
    startDate: new Date(2021, 4, 13, 16),
    endDate: new Date(2021, 4, 13, 17),
    color: 'rgba(0,255,0,0.2)',
    isTimeslot: true,
  },
  {
    id: 5,
    description: 'Actual event', // This is an actual event
    startDate: new Date(2021, 4, 14, 16),
    endDate: new Date(2021, 4, 14, 18, 30),
    color: 'blue',
    isTimeslot: false,
  },
];

const MyComponent = () => (
  <WeekView
    events={myEvents}
    onEventPress={(event) => {
      if (event.isTimeslot) {
        // handle as empty timeslot...
      } else {
        // handle as regular event
      }
    }}
    // ...other props
  />
);

This would look like this: image

Comments:

  • Notice you can use both "actual events" and "timeslot events" in the same events array
  • You can change the isTimeslot for another name if you want.
  • You can provide more elements in the event if necessary, like isTimeslotBusy, or any other information that you may need in the callback.

@jeminsieow does this work for you?

pdpino avatar May 10 '21 17:05 pdpino

This works perfectly! Thanks a lot @pdpino

jeminsieow avatar May 11 '21 07:05 jeminsieow

Hi @pdpino , how will this work if I want the event will be over the colored grid? Something like this:

image

I think the current workaround will halve the "actual event" into 2, something like this:

image

Appreciate the help!

jeminsieow avatar May 24 '21 17:05 jeminsieow

I assume you have two events in tuesday 11: the blue event from 14hrs to 18hrs, and the placeholder event from 14hrs to 23hrs. The events are overlapping, hence their width is reduced so they fit in the day column. To avoid the shrinking and get the expected result, you would have to prevent the events from overlapping, thus you have to change the placeholder event to be from 18hrs to 23hrs.

pdpino avatar May 24 '21 18:05 pdpino

@pdpino That's correct. While I understand the workaround, it really limits the application of the "available timeslots", as events cannot be created on it. For the application I'm using it for, an overlap of the "available dates" and "actual events" is required.

Is it possible to set an array of time ranges, with its own Component, but keep the grid functionality? (ie. onGridClick, and allowing Events to overlap it). Essentially it'll be like the current events array and EventComponent, but it is just for appearance. onGridClick will still work on it and real events will be placed over it when created.

jeminsieow avatar May 25 '21 04:05 jeminsieow

@jeminsieow to make sure I understand correctly: do you need to be able to press the actual events? (using onPressEvent). I suppose the answer is no. If yes, what happens when the user press a point that is overlapping between a placeholder-event and an actual-event? (e.g. tuesday 16hrs). Should the touch go to the actual event (blue) or the placeholder (light green)?

Is it possible to set an array of time ranges, with its own Component, but keep the grid functionality? (ie. onGridClick, and allowing Events to overlap it).

Regarding this, there is no way to keep a separate array of events that are not reduced in width when there is overlap, something like "background-events". However, if the onEventPress prop is not passed, onGridClick should receive any touch in the grid (even those over the events).


If I understood correctly what you need, and assuming the answer to my question is no, I think you can make it work like this:

  1. Use onGridClick instead of onEventPress:

    • Remove onEventPress (i.e. do not pass anything, or pass onEventPress={null}).
    • Use onGridClick to receive presses in any point of the grid (including where there is an event). When the events are not pressable (onEventPress == null), any press should be ignored by the event box and passed to the grid, triggering onGridClick() (I have not tested this recently, but I have seen it before. I will check again soon).
  2. Decouple available-slots and placeholder-events:

    • Available-slots will be the actual time ranges that are available
    • Placeholder-events will represent the available-slots in the grid (the green boxes), but making sure they do not overlap with actual-events. In other words, placeholder-events will represent the available-slots that do not have any actual-events saved yet.
    • If you create an actual-event where there is an available-slot, you should remove or reduce the placeholder-event to avoid the overlap (e.g. if you create the blue event from 14hrs to 18hrs, then you reduce the placeholder from 18hrs to 23hrs)
    • If that slot is still available, you should keep that information elsewhere (hence, the decoupling)
    • This will also allow you to decide how many events you allow per available-slot, without changing the placeholder-events
    • Pass actual-events and placeholder-events (without overlap) in the events prop

So, your code would be something like this:

const availableTimeSlots = {} // Save this in any convenient way

const myEvents = [
  // actual events...
  // placeholder events without overlap...
];

const MyComponent = () => (
  <WeekView
    events={myEvents}
    onGridClick={(pressEvent, hour, date) => {
      // Here you need to check if the slot is available, by checking availableTimeSlots
      if (isSlotAvailable(availableTimeSlots, hour, date)) {
        // handle as available slot

        // If you create a new actual-event that will use that place,
        // make sure to remove or reduce the corresponding placeholder-event
      } else {
        // handle as non-available place on the grid
      }
    }}
    // ...other props
  />
);

Does that make sense to you? Am I understanding the problem correctly?

pdpino avatar May 25 '21 22:05 pdpino

As a side note, consider onGridClick only provides precision up to the hour (not minutes or seconds). If you need, we could add precision up to the minutes, although the press with the fingers is usually not so precise.

pdpino avatar May 25 '21 22:05 pdpino

Hi @pdpino, thanks for the reply.

@jeminsieow to make sure I understand correctly: do you need to be able to press the actual events? (using onPressEvent). I suppose the answer is no. If yes, what happens when the user press a point that is overlapping between a placeholder-event and an actual-event? (e.g. tuesday 16hrs). Should the touch go to the actual event (blue) or the placeholder (light green)?

For this, I actually do need to press the actual events. In the case that the actual event overlaps the placeholder-event, the touch will go to the actual event and not the placeholder. (This behaves like onEventPress having priority over onGridPress).

The placeholder (green spaces) is just a change in background of the grid.

Really appreciate the help!

jeminsieow avatar Jun 07 '21 04:06 jeminsieow

@jeminsieow I see. For now, I think we cannot implement a specific solution for this problem, something like backgroundEvents=[...] or timeslots=[...] (placeholder-events that would be placed in the grid, but do not get halved if there is an overlap, and have less priority than actual-events). It looks like this is too specific, and could still be solved in your app code (see next comment). Also, the implementation inside week-view would get messy, as we'd have to handle two array of events (the regular events and the new ones), handle touch conflicts and z-index conflicts between events, and more.

If we see more use-cases for this in the future, or think of a different approach to tackle this, or if @hoangnm thinks otherwise, I'd be happy to take a shot at it :slightly_smiling_face: UPDATE (october 2021): #140 mentioned "business hours", which is a similar use-case to this. We can definitely address both cases (still in the future, though)

pdpino avatar Jun 07 '21 19:06 pdpino

I think the current solution would be:

  • Using onEventPress as press-callback, and distinguish actual-events vs placeholder-events with a boolean like isTimeslot (like the first example I provided)
  • Providing actual-events and placeholder-events in the events prop
  • Decouple available-time-slots and placeholder-events (like the last example I provided), and making sure placeholder-events do not overlap with actual-events (so the width is not halved)

Although is not perfect, it should work (unless I'm missing something)

pdpino avatar Jun 07 '21 19:06 pdpino

Update: now you can create blocks with colors to achieve this

timeslots

The light green events do not get shrank, cannot be pressed neither dragged.

const timeslotFields = {
  description: '',
  color: 'rgba(0,255,0,0.2)',
  eventKind: 'block',
  style: {
    borderWidth: 0,
  },
  disablePress: true,
  disableDrag: true,
  disableLongPress: true,
};
const sampleEvents = [
  // Regular events
  {
    id: 1,
    description: 'Event 1',
    startDate: new Date(2023, 1, 21, 15),
    endDate: new Date(2023, 1, 21, 16, 30),
    color: 'blue',
  },
  {
    id: 2,
    description: 'Event 2',
    startDate: new Date(2023, 1, 23, 12),
    endDate: new Date(2023, 1, 23, 14, 30),
    color: 'orange',
  },
  {
    id: 3,
    description: 'Event 3',
    startDate: new Date(2023, 1, 21, 12),
    endDate: new Date(2023, 1, 21, 16),
    color: 'red',
  },
  // Timeslot events:
  {
    id: 10,
    startDate: new Date(2023, 1, 20, 9),
    endDate: new Date(2023, 1, 20, 17),
    ...timeslotFields,
  },
  {
    id: 11,
    startDate: new Date(2023, 1, 21, 9),
    endDate: new Date(2023, 1, 21, 17),
    ...timeslotFields,
  },
  {
    id: 12,
    startDate: new Date(2023, 1, 22, 9),
    endDate: new Date(2023, 1, 22, 17),
    ...timeslotFields,
  },
  {
    id: 13,
    startDate: new Date(2023, 1, 23, 9),
    endDate: new Date(2023, 1, 23, 17),
    ...timeslotFields,
  },
  {
    id: 14,
    startDate: new Date(2023, 1, 24, 9),
    endDate: new Date(2023, 1, 24, 13),
    ...timeslotFields,
  },
];

pdpino avatar Feb 22 '23 22:02 pdpino