diff --git a/public/favicon.ico b/public/favicon.ico deleted file mode 100644 index a11777c..0000000 Binary files a/public/favicon.ico and /dev/null differ diff --git a/public/index.html b/public/index.html index aa069f2..0eb6c96 100644 --- a/public/index.html +++ b/public/index.html @@ -2,7 +2,6 @@ - {count} 일

- ); -} - -function DateRangePicker({ onChangeRange }) { - const [dateRange, setRange] = useState({ - startDate: new Date(), - endDate: new Date(), - key: 'dateRange' - }); - const handleChangeDateRange = ({ dateRange }) => { - const range = new Range(dateRange.startDate, dateRange.endDate); - - setRange(dateRange); - onChangeRange(range); - }; - - return ( - handleChangeDateRange(item)} - - ranges={[dateRange]} - dateDisplayFormat={'yyyy/MM/dd'} - /> - ); -} +import { DateRangePicker } from './components/DateRangePicker'; +import { CountResult } from './components/CountResult'; function App({ counter }) { const [count, setCount] = useState(0); - const handleChangeRange = async (range) => { - const newCount = await counter.countWeekdayInRange(range); - setCount(newCount); + const handleChangeRange = (range) => { + counter.countWeekdayInRange(range).then(setCount); }; return ( diff --git a/src/cache/holidayCache.js b/src/cache/holidayCache.js deleted file mode 100644 index ceb0e62..0000000 --- a/src/cache/holidayCache.js +++ /dev/null @@ -1,20 +0,0 @@ -import format from 'date-fns/format'; - -function getKey(date) { - return format(date, 'yyyy/MM'); -} - -export default class HolidayCache { - constructor(dataSource) { - this.dataSource = dataSource; - this.cache = {}; - } - - getHolidaysIn = async (month) => { - const key = getKey(month); - if (!(key in Object.keys(this.cache))) { - this.cache[key] = await this.dataSource.getHolidaysIn(month); - } - return this.cache[key]; - } -} \ No newline at end of file diff --git a/src/components/CountResult.js b/src/components/CountResult.js new file mode 100644 index 0000000..2abb9fd --- /dev/null +++ b/src/components/CountResult.js @@ -0,0 +1,7 @@ +import React from 'react'; + +export function CountResult({ count }) { + return ( +

{count} 일

+ ); +} diff --git a/src/components/DateRangePicker.js b/src/components/DateRangePicker.js new file mode 100644 index 0000000..bceadcf --- /dev/null +++ b/src/components/DateRangePicker.js @@ -0,0 +1,30 @@ +import React, { useState } from 'react'; +import * as locales from 'react-date-range/dist/locale'; +import { DateRange } from 'react-date-range'; +import Range from '../domain/range'; + +import 'react-date-range/dist/styles.css'; +import 'react-date-range/dist/theme/default.css'; + +export function DateRangePicker({ onChangeRange }) { + const [dateRange, setRange] = useState({ + startDate: new Date(), + endDate: new Date(), + key: 'dateRange' + }); + const handleChangeDateRange = ({ dateRange }) => { + const range = new Range(dateRange.startDate, dateRange.endDate); + + setRange(dateRange); + onChangeRange(range); + }; + + return ( + handleChangeDateRange(item)} + ranges={[dateRange]} + dateDisplayFormat={'yyyy/MM/dd'} /> + ); +} diff --git a/src/dataSource/herokuHolidayDataSource.js b/src/dataSource/herokuHolidayDataSource.js index 50d94f3..bc3d6d3 100644 --- a/src/dataSource/herokuHolidayDataSource.js +++ b/src/dataSource/herokuHolidayDataSource.js @@ -1,5 +1,10 @@ import axios from 'axios'; -import { format } from 'date-fns'; +import { isBefore } from 'date-fns'; +import addMonths from 'date-fns/addMonths'; +import isWeekend from 'date-fns/isWeekend'; +import isWithinInterval from 'date-fns/isWithinInterval'; +import startOfMonth from 'date-fns/startOfMonth'; +import Month from './month'; function readDate(dateString) { const year = parseInt(dateString.substr(0, 4)); @@ -9,17 +14,39 @@ function readDate(dateString) { return new Date(year, month - 1, day); } -async function getHolidaysIn(month) { - const params = { - year: format(month, 'yyyy'), - month: format(month, 'MM') - }; - const apiUrl = `https://shielded-forest-67184.herokuapp.com/holidays?year=${params.year}&month=${params.month}`; - const { data: { dates } } = await axios.get(apiUrl); +const cache = {}; + +async function getHolidaysInMonth({ year, month }) { + const apiUrl = `https://shielded-forest-67184.herokuapp.com/holidays?year=${year}&month=${month}`; + + if (cache[apiUrl] === undefined) { + const { data: { dates } } = await axios.get(apiUrl); + const holidays = dates.map(readDate); + cache[apiUrl] = holidays; + } + return cache[apiUrl]; +} + +function* monthsInRange(range) { + const {start, end} = range; + const endMonth = startOfMonth(addMonths(end, 1)); + let currentMonth = startOfMonth(start); + + while(isBefore(currentMonth, endMonth)) { + yield new Month(currentMonth); + currentMonth = addMonths(currentMonth, 1); + } +} + +async function getHolidaysInRange(range) { + const months = Array.from(monthsInRange(range)); - return dates.map(readDate); + return (await Promise.all(months.map(getHolidaysInMonth))) + .flat() + .filter(date => isWithinInterval(date, range)) + .filter(date => !isWeekend(date)); } export { - getHolidaysIn + getHolidaysInRange } \ No newline at end of file diff --git a/src/dataSource/month.js b/src/dataSource/month.js new file mode 100644 index 0000000..4f63b21 --- /dev/null +++ b/src/dataSource/month.js @@ -0,0 +1,13 @@ +export default class Month { + constructor(date) { + const year = date.getFullYear(); + const month = date.getMonth() + 1; + + this.year = year.toString(); + this.month = month.toString().padStart(2, '0'); + } + + toString() { + return this.year + this.month; + } +} \ No newline at end of file diff --git a/src/domain/weekdayCounter.js b/src/domain/weekdayCounter.js index 35e9b69..090cef1 100644 --- a/src/domain/weekdayCounter.js +++ b/src/domain/weekdayCounter.js @@ -1,16 +1,4 @@ -import { differenceInBusinessDays, addDays, isWithinInterval, startOfMonth, addMonths, isBefore, isWeekend } from 'date-fns'; - -function* monthsInRange(range) { - const {start, end} = range; - const endMonth = startOfMonth(addMonths(end, 1)); - - let currentMonth = startOfMonth(start); - - while(isBefore(currentMonth, endMonth)) { - yield currentMonth; - currentMonth = addMonths(currentMonth, 1); - } -} +import { differenceInBusinessDays, addDays } from 'date-fns'; function countAllNonWeekendIn(range) { const {start, end} = range; @@ -19,19 +7,10 @@ function countAllNonWeekendIn(range) { return differenceInBusinessDays(nextEndDate, start); } -async function countWeekdayHolidaysIn(dataSource, range) { - const months = Array.from(monthsInRange(range)); - const holidaysInRange = (await Promise.all(months.map(dataSource.getHolidaysIn))) - .flat() - .filter(date => isWithinInterval(date, range)) - .filter(date => !isWeekend(date)); - - return holidaysInRange.length; -} - async function countWeekdayInRange(dataSource, range) { const nonWeekendCount = countAllNonWeekendIn(range); - const holidaysCount = await countWeekdayHolidaysIn(dataSource, range); + const holidaysInRange = await dataSource.getHolidaysInRange(range); + const holidaysCount = holidaysInRange.length; return nonWeekendCount - holidaysCount; } diff --git a/src/index.js b/src/index.js index 921cd6b..fc4114c 100644 --- a/src/index.js +++ b/src/index.js @@ -3,12 +3,11 @@ import React from 'react'; import App from './App'; import WeekdayCounter from './domain/weekdayCounter'; import * as HerokuHolidayDataSource from './dataSource/herokuHolidayDataSource'; -import HolidayCache from './cache/holidayCache'; ReactDOM.render( , document.getElementById('root')