Nicotine Powered Man

    timetable-calculator

    1.0.3 • Public • Published

    timetable-calculator

    NPM version Build status Code coverage

    A library that helps you to calculate your time slots for rendering timesheet or timetable.

    This library was generated by essay(https://github.com/dtinth/essay).

    Getting started

    1. Install this library
    npm i --save timetable-calculator
    
    1. import function from this library
    import { generateTimetable } from 'timetable-calculator'

    API

    generateTimetable is function that calculate time slot.

    // main.js
    export { generateTimetable } from './timetable-generator'

    Example

    generateTimetable(timeslot) receive timeslot as input that is array with key name, start, duration and row.

    generateTimetable will return overlap in row and sort by start and row.

    // timetable-generator.test.js
    import { expect } from 'chai'
    import { generateTimetable } from './timetable-generator'
     
    describe('generateTimetable', () => {
      it('should generate when pass full timeslot', () => {
        const timeslot = [
          { name: 'Algorithm', start: 0, duration: 3, row: 0 },
          { name: 'TDD', start: 2, duration: 4, row: 0 },
          { name: 'Javascript', start: 1, duration: 3, row: 1 },
          { name: 'Agile & Scrum', start: 3, duration: 3, row: 1 },
          { name: 'Web Development', start: 5, duration: 3, row: 2 },
          { name: 'Docker', start: 1, duration: 4, row: 2 },
          { name: 'CI', start: 2, duration: 2, row: 1 }
        ]
        expect(generateTimetable(timeslot)).to.eql([
          { name: 'Algorithm', start: 0, duration: 3, row: 0, overlap: 0 },
          { name: 'TDD', start: 2, duration: 4, row: 0, overlap: 1 },
          { name: 'Javascript', start: 1, duration: 3, row: 1, overlap: 0 },
          { name: 'CI', start: 2, duration: 2, row: 1, overlap: 1 },
          { name: 'Agile & Scrum', start: 3, duration: 3, row: 1, overlap: 2 },
          { name: 'Docker', start: 1, duration: 4, row: 2, overlap: 0 },
          { name: 'Web Development', start: 5, duration: 3, row: 2, overlap: 0 }
        ])
      })
    })

    Implementation

    generateTimetable is a function that contains

    • Slot preparation (createSortedChunkRow, getSizeAllSlots, sortSlotByStart, createFreeTimeslot)
    • Handle overlapping (findOverlapIndex, updateCountOverlapSlot)
    // timetable-generator.js
    import _ from 'lodash'
    import { createSortedChunkRow, getSizeAllSlots, sortSlotByStart, createFreeTimeslot } from './slot-preparation'
    import { findOverlapIndex, updateCountOverlapSlot } from './overlap-slot'
     
    export const generateTimetable = (slots) => {
      let timetable = []
      const chunks = createSortedChunkRow(slots)
      const fullsize = getSizeAllSlots(slots)
      _.forEach(chunks, (chunk) => { // Todo map or foreach
        const sortedChunk = sortSlotByStart(chunk)
        let countOverlapSlot = createFreeTimeslot(fullsize)
        _.forEach(sortedChunk, (newSlot) => {
          const overlap = findOverlapIndex(countOverlapSlot, newSlot)
          timetable.push({ ...newSlot, overlap })
          countOverlapSlot = updateCountOverlapSlot(countOverlapSlot, newSlot)
        })
      })
      return timetable
    }

    Prepare Slot

    There are mini functions that can prepare slot before integration.

    // slot-preparation.js
    import _ from 'lodash'
     
    export const createFreeTimeslot = (size) => _.range(0, size, 0)
    export const isInTimeRange = (timeTarget, start, end) => _.inRange(timeTarget, start, end + 1)
    export const sortSlotByStart = (slots) => _.sortBy(slots, (slot) => slot.start)
    export const getEnd = (start, duration) => start + duration - 1
     
    export const getSizeAllSlots = (slots) => {
      const { start, duration } = _.maxBy(slots, (slot) => {
        const { start, duration } = slot
        return getEnd(start, duration) + 1
      })
      return getEnd(start, duration) + 1
    }
     
    export const createSortedChunkRow = (slots) => {
      let chunks = []
      _.map(slots, (slot) => {
        const index = slot.row
        if (chunks[index]) {
          chunks[index].push(slot)
        }
        else {
          chunks[index] = [slot]
        }
      })
      return chunks
    }
     
    export const convertSlotToArray = (slot) => {
      const { start, duration } = slot
      const end = getEnd(start, duration)
      const size = end + 1
      return _.range(size).map((value, index) => isInTimeRange(index, start, end) ? 1 : 0)
    }

    Testing

    There are some examples about simple results of them.

    // slot-preparation.test.js
    import { expect } from 'chai'
    import {
      createFreeTimeslot,
      isInTimeRange,
      getEnd,
      getSizeAllSlots,
      sortSlotByStart,
      createSortedChunkRow,
      convertSlotToArray
    } from './slot-preparation'
     
    describe('slot-preparation', () => {
      it('should create free timeslot with input size', () => {
        expect(createFreeTimeslot(3)).to.eql([0, 0, 0])
      })
     
      it('should be true because 5 is in range [2, 5]', () => {
        const timeTarget = 5
        const start = 2
        const end = 5
        expect(isInTimeRange(timeTarget, start, end)).to.equal(true)
      })
     
      it('should be false because 3 is not in range when start with 4 and duration with 2', () => {
        const timeTarget = 3
        const start = 4
        const duration = 2
        expect(isInTimeRange(timeTarget, start, getEnd(start, duration))).to.equal(false)
      })
     
      it('should calculate correct end slot when pass start and duration', () => {
        const start = 3
        const duration = 5
        expect(getEnd(start, duration)).to.equal(7)
      })
     
      it('should get size all slots', () => {
        const timeslot = [{ start: 0, duration: 3 }, { start: 1, duration: 4 }]
        expect(getSizeAllSlots(timeslot)).to.equal(5)
      })
     
      it('should sort slots by start', () => {
        const initialTimeslots = [{ start: 2 }, { start: 0 }, { start: 1 }]
        const sortedSlots = [{ start: 0 }, { start: 1 }, { start: 2 }]
        const actual = sortSlotByStart(initialTimeslots)
        expect(sortedSlots).to.eql(actual)
      })
     
      it('should chunk and sort by row', () => {
        const initialTimeslots = [{ row: 0 }, { row: 2 }, { row: 0 }, { row: 1 }]
        const sortedChunkRow = [[{ row: 0 }, { row: 0 }], [{ row: 1 }], [{ row: 2 }]]
        expect(createSortedChunkRow(initialTimeslots)).to.eql(sortedChunkRow)
      })
     
      it('should convert slot to array', () => {
        expect(convertSlotToArray({ start: 1, duration: 3 })).to.eql([0, 1, 1, 1])
      })
    })

    Handle overlap slot

    updateCountOverlapSlot When it receives new slot, it will sum all slot each columns.

    findOverlapIndex When slots overlap in the same row, it will return a key (overlap) as mini row.

    // overlap-slot.js
    import _ from 'lodash'
    import { convertSlotToArray, getEnd, isInTimeRange } from './slot-preparation'
     
    export const updateCountOverlapSlot = (countOverlapSlot, newSlot) => {
      const newSlotArray = convertSlotToArray(newSlot)
      return _.map(countOverlapSlot, (sumColumnSlot, index) => (
        sumColumnSlot + (newSlotArray[index] || 0)
      ))
    }
     
    export const findOverlapIndex = (countOverlapSlot, newSlot) => {
      const size = countOverlapSlot.length
      const { start, duration } = newSlot
      const end = getEnd(start, duration)
      const overlapTargetSlot = _.map(countOverlapSlot, (sumColumn, index) => {
        return isInTimeRange(index, start, end) ? sumColumn - 1 : 0
      })
      const newSlotArray = convertSlotToArray(newSlot)
      const overlapIndex = _.max(
        _.map(_.range(size), (index) => {
          return overlapTargetSlot[index] + (newSlotArray[index] || 0)
        })
      )
      return overlapIndex
    }

    Testing

    There are some examples how they can be used.

    // overlap-slot.test.js
    import { expect } from 'chai'
    import { findOverlapIndex, updateCountOverlapSlot } from './overlap-slot'
     
    describe('overlap-slot', () => {
      it('should find index of overlap slots when it overlap', () => {
        const allSlot = [1, 0, 0, 0, 0];
        const newSlot = { start: 0, duration: 2 }
        expect(findOverlapIndex(allSlot, newSlot)).to.equal(1)
      })
     
      it('should find index of overlap slots when it doesn\'t overlap', () => {
        const allSlot = [0, 0, 0, 0];
        const newSlot = { start: 1, duration: 2 }
        expect(findOverlapIndex(allSlot, newSlot)).to.equal(0)
      })
     
      it('should update count overlap slot when it has new slot', () => {
        const countOverlapSlot = [0, 1, 2, 1, 0]
        const newSlot = { start: 2, duration: 2 }
        expect(updateCountOverlapSlot(countOverlapSlot, newSlot)).to.eql([0, 1, 3, 2, 0])
      })
    })

    Install

    npm i timetable-calculator

    DownloadsWeekly Downloads

    3

    Version

    1.0.3

    License

    MIT

    Last publish

    Collaborators

    • zugarzeeker