JavaScript transform object array to specific date category array

80 views Asked by At

I have a plain array of objects with a date as an attribute and want to transform that array based on that date with a specific date category.

const orders = [
  {
    "order_id": "1",
    "description": "Description 1",
    "date": "2024-02-03T19:00:57.744Z",
  },
  {
    "order_id": "2",
    "description": "Description 2",
    "date": "2024-02-04T19:00:57.744Z",
  },
  {
    "order_id": "3",
    "description": "Description 3",
    "date": "2024-02-06T19:00:57.744Z",
  },
]

Along with this, I have a date categories as below:

const category = [
  'Overdue',
  'Today',
  'Tomorrow',
  'This Week',
  'Next Week',
  'This Month',
  'Next Month',
  'Later',
  'No Date',
];

I need to transform a new array with specific date categories and categories should be sorted in this manner only (increasing date).

Expected output:

[
  {
    "category": "Today",
    "data": [
      {
        "order_id": "1",
        "description": "Description 1",
        "date": "2024-02-03T19:00:57.744Z"
      }
    ]
  },
  {
    "category": "Tomorrow",
    "data": [
      {
        "order_id": "2",
        "description": "Description 2",
        "date": "2024-02-04T19:00:57.744Z"
      }
    ]
  },
  {
    "category": "This Week",
    "data": [
      {
        "order_id": "3",
        "description": "Description 3",
        "date": "2024-02-06T19:00:57.744Z"
      }
    ]
  }
]

I'm using dayjs for date manipulations, and using the below function to check the date for a specific category

const getCategoryByDate = date => {

    if (date === null || date === undefined) {
      return 'No Date';
    }

    const isToday = dayjs(date).isToday();
    const isTomorrow = dayjs(date).isTomorrow();
    const overdue = dayjs().isAfter(date, 'day');
    const isThisYear = dayjs().year() === dayjs(date).year();
    const isThisWeek = isThisYear && dayjs().isoWeek() === dayjs(date).isoWeek();
    const isNextWeek = isThisYear && dayjs().isoWeek() + 1 === dayjs(date).isoWeek();
    const isThisMonth = isThisYear && dayjs().month() === dayjs(date).month();
    const isNextMonth = isThisYear && dayjs().month() + 1 === dayjs(date).month();

    if (overdue) {
      return 'Overdue';
    } else if (isToday) {
      return 'Today';
    } else if (isTomorrow) {
      return 'Tomorrow';
    } else if (isThisWeek) {
      return 'This Week';
    } else if (isNextWeek) {
      return 'Next Week';
    } else if (isThisMonth) {
      return 'This Month';
    } else if (isNextMonth) {
      return 'Next Month';
    }
    return 'Later';
  };

and finally using the array.reduce function to transform new desired array

const output = orders.reduce((acc, curr) => {
    const {date} = curr;
    const category = getCategoryByDate(date);
    const found = acc.find((item) => item.category === category);
    if (found) {
      found.data.push(curr);
    } else {
      const obj = {category: category, data: [curr]};
      acc = [obj];
    }
    return acc;
  }, []);

With this, I'm not getting the desired result, output array is not sorted in order according to category if the order array is not sorted previously.

1

There are 1 answers

0
Alexander Nenashev On

Convert your categories to an object with values of dates (generate them as strings with dayjs or vanilla js), then use Array::findLast() to find a category.

This will be more performant than calling date functions on each comparison.

If your dates aren't sorted, sort them afterwards based on index keys of the categories.

const orders = [
  {
    "order_id": "1",
    "description": "Description 1",
    "date": "2024-02-03T19:00:57.744Z",
  },
  {
    "order_id": "2",
    "description": "Description 2",
    "date": "2024-02-04T19:00:57.744Z",
  },
  {
    "order_id": "3",
    "description": "Description 3",
    "date": "2024-02-06T19:00:57.744Z",
  },
];

// generate dynamically
const categories = {
  Today: '2024-02-03',
  Tomorrow: '2024-02-04',
  'This week': '2024-02-05',
  // add more entries
};

const result = orders.reduce(((map, categories) => (r, item) => {
  if(!item.date){
    // add to no date cateogory
    return r;
  }
  const cat = categories.findLast(([title, date]) => date < item.date);
  if(!cat){
    // add to overdue category
    return r;
  }
  (map[cat[0]] ??= r[r.length] = {category: cat[0], data: []}).data.push(item);
  return r;
  
})({}, Object.entries(categories)), []);

console.log(result);