ํ‹ฐ์Šคํ† ๋ฆฌ ๋ทฐ

๋‹ฌ๋ ฅ์ด๋ผ๋ฉด ํ•„์š”ํ•œ ๊ณตํœด์ผ !

 

๊ณต๊ณต๋ฐ์ดํ„ฐ API ๋ฐœ๊ธ‰

 

๊ณตํœด์ผ ์ •๋ณด๋ฅผ ์–ป์–ด์˜ค๊ธฐ ์œ„ํ•ด์„œ ๊ณต๊ณต๋ฐ์ดํ„ฐ๋ฅผ ์ด์šฉํ•˜์˜€๋‹ค.

 

ํšŒ์›๊ฐ€์ž…๊ณผ ๋กœ๊ทธ์ธ์„ ํ•˜๊ณ  ํ™œ์šฉ์‹ ์ฒญ์„ ํด๋ฆญํ•œ๋‹ค.

์‹ ์ฒญ์ด ์™„๋ฃŒ๋˜๋ฉด ์ผ๋ฐ˜ ์ธ์ฆํ‚ค ๋‘ ๊ฐœ๊ฐ€ ๋ฐœ๊ธ‰๋˜๋Š”๋ฐ, Encoding ํ‚ค๋ฅผ ์ด์šฉํ•˜์˜€๋‹ค.

 

 

API ์ ์šฉ

 

๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ

.env.local ํŒŒ์ผ์— ํ‚ค ๊ฐ’์„ ๋‘˜ ๋•Œ ํ•œ ๊ฐ€์ง€ ์œ ์˜ํ•  ์ ์ด ์žˆ๋‹ค.

supabase์˜ ์–ด๋…ผํ‚ค ๊ฐ™์€ ๊ฒฝ์šฐ๋Š” ๋ธŒ๋ผ์šฐ์ €์— ๋…ธ์ถœ๋˜์–ด๋„ ํฌ๊ฒŒ ๋ฌธ์ œ๊ฐ€ ์•ˆ ๋˜๊ธฐ ๋•Œ๋ฌธ์— 'NEXT_PUBLIC'์„ ๋ถ™์—ฌ์„œ ์‚ฌ์šฉํ•œ๋‹ค.

 

ํ•˜์ง€๋งŒ ๊ณต๊ณต๋ฐ์ดํ„ฐ ๊ฐ™์€ ๊ฒฝ์šฐ๋Š” ํšŒ์›๊ฐ€์ž… ํ›„ ๋ฐœ๊ธ‰๋ฐ›์•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ธŒ๋ผ์šฐ์ €์— ๋…ธ์ถœ์ด ์กฐ๊ธˆ ๊บผ๋ ค์กŒ๋‹ค.

๋”ฐ๋ผ์„œ 'NEXT_PUBLIC'์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•˜๋‹ค.

const ENV = {
  SUPABASE_URL: process.env.NEXT_PUBLIC_SUPABASE_URL || '',
  SUPABASE_ANON_KEY: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || '',
  HOLIDAY_KEY: process.env.HOLIDAY_KEY || '',
};

export default ENV;

 

 

๊ทธ๋ ‡๋‹ค๋ฉด ์ด ํ‚ค๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์–ด๋–ป๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์™€์•ผ ํ• ๊นŒ?

์ฒ˜์Œ์—๋Š” ๊ธฐ์กด ๋ฐฉ์‹์„ ๋”ฐ๋ผ service.ts ํŒŒ์ผ์—์„œ fetch๋ฅผ ํ†ตํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ฐ€์ ธ์™”์—ˆ๋‹ค.

// ๊ณต๊ณต๋ฐ์ดํ„ฐ ๊ณตํœด์ผ API
export const getHolidays = async (year: string) => {
  const BASE_URL = 'http://apis.data.go.kr/B090041/openapi/service/SpcdeInfoService';
  const res = await fetch(
    //ํ˜„์žฌ ๋…„๋„ ๊ฐ’๋งŒ
    `${BASE_URL}/getRestDeInfo?ServiceKey=${ENV.HOLIDAY_KEY}&solYear=${year}&_type=json&numOfRows=100`
  );
  const data = await res.json();

  return data;
};

 

ํ•˜์ง€๋งŒ 'NEXT_PUBLIC'๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์— ๋ธŒ๋ผ์šฐ์ €์—์„œ์˜ ์‚ฌ์šฉ์ด ๋ถˆ๊ฐ€๋Šฅํ–ˆ๋‹ค.

 

๋”ฐ๋ผ์„œ ์ƒ๊ฐํ•œ ๊ฒƒ์ด ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ!

์„œ๋ฒ„-์„œ๋ฒ„์˜ ํ†ต์‹ ์„ ํ†ตํ•ด ๋ฐ์ดํ„ฐ์˜ ์‘๋‹ต ๊ฐ’์„ ๋ผ์šฐํŠธ ๊ฒฝ๋กœ์— ์ฃผ๊ธฐ ๋•Œ๋ฌธ์—

์„œ๋ฒ„์—์„œ ํ‚ค ๊ฐ’ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ ํŽ˜์นญ์ด ๊ฐ€๋Šฅํ•˜๋ฉฐ ๊ทธ ๋ฐ์ดํ„ฐ๋ฅผ ๋ธŒ๋ผ์šฐ์ €์—์„œ๋„ ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

 

๐Ÿ“๊ณตํœด์ผ API ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ

// app > api > holiday > route.ts
import { NextResponse } from 'next/server';

// ๊ณตํœด์ผ ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ
export async function GET(req: Request) {
  const { searchParams } = new URL(req.url);
  const year = searchParams.get('year') || new Date().getFullYear().toString();

  const BASE_URL = 'http://apis.data.go.kr/B090041/openapi/service/SpcdeInfoService';
  const HOLIDAY_KEY = process.env.HOLIDAY_KEY;

  const url = `${BASE_URL}/getRestDeInfo?ServiceKey=${HOLIDAY_KEY}&solYear=${year}&_type=json&numOfRows=100`;

  const res = await fetch(url);
  const data = await res.json();

  return NextResponse.json(data);
}

 

 

 

๋ฐ์ดํ„ฐ ๊ฐ€๊ณต

 

๋ผ์šฐํŠธํ•ธ๋“ค๋Ÿฌ๋ฅผ ํ†ตํ•ด ๋ฐ›์•„์˜จ ์‘๋‹ต๊ฐ’์„ fetch๋กœ ๋ฐ›์•„์˜จ๋‹ค.

export const useGetHolidays = (calendarYear: string) => {
  // ๊ณตํœด์ผ ๋ฐ์ดํ„ฐ ์ „์ฒด ๊ฐ€์ ธ์˜ค๊ธฐ
  const fetchHolidays = async () => {
    const res = await fetch(`/api/holidays?year=${calendarYear}`); // ๋…„๋„์— ๋งž๋Š” ๋ฐ์ดํ„ฐ
    const data = await res.json();
    const items = data.response?.body?.items?.item || [];
    
    ...

 

์ด ๊ฐ’์€ ๋‹ฌ๋ ฅ์— ๋ฐ”๋กœ ์ ์šฉํ•  ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ์•Œ๋งž๊ฒŒ ๊ฐ€๊ณตํ•ด์•ผ ํ•œ๋‹ค.

 

 

1. ํ•„์š”ํ•œ ๊ฐ’๋งŒ ์ถ”์ถœ

  • ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ์—์„œ ํ•„์š”ํ•œ ๋‚ ์งœ์™€ ๊ณตํœด์ผ๋ช…์„ ๋ฝ‘์•„๋‚ธ๋‹ค.
// ํ•„์š”ํ•œ ๊ฐ’๋งŒ ์ถ”์ถœ
    const holidays = items.map((item: Item) => ({
      date: item.locdate, // 20250408 ํ˜•์‹
      name: item.dateName,
    }));

 

 

 

 

2. ๋‹ฌ๋ ฅ์— ์ ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ฐ€๊ณต

  • .slice ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ์ˆซ์ž์˜€๋˜ date๋ฅผ ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ dateStr ๋ณ€์ˆ˜์— ๋‹ด์•„์ค€๋‹ค.
  • '20250101' ํ˜•ํƒœ์˜ date๋ฅผ ๋…„, ์›”, ์ผ๋กœ ๋‚˜๋ˆ„์–ด new Date ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•œ๋‹ค.
    • ์ด๋•Œ, ์›”์€ 0๋ถ€ํ„ฐ ์‹œ์ž‘ํ•˜๋Š” ์ธ๋ฑ์Šค์ด๊ธฐ ๋•Œ๋ฌธ์— '-1' ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์ค€๋‹ค.
 // ์บ˜๋ฆฐ๋”์— ์ ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ฐ€๊ณต
    const formattedHolidays = holidays.map((item: Holidays) => {
      const dateStr = item.date.toString(); // number → string

      return {
        title: item.name,
        date: new Date(
          parseInt(dateStr.slice(0, 4), 10), // year
          parseInt(dateStr.slice(4, 6), 10) - 1, // month (0-indexed)
          parseInt(dateStr.slice(6, 8), 10) // day
        ),
      };
    });
    return formattedHolidays;
  };

 

 

 

3. useQuery ํ›…์„ ๋ฐ˜ํ™˜

  • ์ด ๋ชจ๋“  ๊ธฐ๋Šฅ์˜ ํ•จ์ˆ˜๋ฅผ queryFn๋กœ ๊ฐ–๋Š” useQuery๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.
  • ๊ณตํœด์ผ์˜ ์ •๋ณด๋ฅผ ๋…„๋งˆ๋‹ค ์บ์‹ฑํ•˜๊ธฐ ์œ„ํ•ด ์ฟผ๋ฆฌ ํ‚ค๋ฅผ ํ•ด๋‹น ๋…„๋„์— ๋”ฐ๋ผ ๋™์ ์œผ๋กœ ๋ณ€๋™ํ•œ๋‹ค.
 return useQuery({
    queryKey: [QUERY_KEY.HOLIDAYS, calendarYear], // ๋…„๋„์— ๋”ฐ๋ผ ์ฟผ๋ฆฌํ‚ค ๋ณ€๋™
    queryFn: fetchHolidays,
    staleTime: 1000 * 60 * 60 * 24, // 1์ผ
  });

 

 

 

๐Ÿ“ŒuseQuery ์ปค์Šคํ…€ ํ›… ์ „์ฒด ๋กœ์ง

import { QUERY_KEY } from '@/constants/queryKey';
import { Holidays, Item } from '@/types/plans';
import { useQuery } from '@tanstack/react-query';

export const useGetHolidays = (calendarYear: string) => {
  // ๊ณตํœด์ผ ๋ฐ์ดํ„ฐ ์ „์ฒด ๊ฐ€์ ธ์˜ค๊ธฐ
  const fetchHolidays = async () => {
    const res = await fetch(`/api/holidays?year=${calendarYear}`); // ๋…„๋„์— ๋งž๋Š” ๋ฐ์ดํ„ฐ
    const data = await res.json();
    const items = data.response?.body?.items?.item || [];

    // ํ•„์š”ํ•œ ๊ฐ’๋งŒ ์ถ”์ถœ
    const holidays = items.map((item: Item) => ({
      date: item.locdate, // 20250408 ํ˜•์‹
      name: item.dateName,
    }));

    // ์บ˜๋ฆฐ๋”์— ์ ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ฐ€๊ณต
    const formattedHolidays = holidays.map((item: Holidays) => {
      const dateStr = item.date.toString(); // number → string
      return {
        title: item.name,
        date: new Date(
          parseInt(dateStr.slice(0, 4), 10), // year
          parseInt(dateStr.slice(4, 6), 10) - 1, // month (0-indexed)
          parseInt(dateStr.slice(6, 8), 10) // day
        ),
      };
    });
    console.log('formattedHolidays', formattedHolidays);
    return formattedHolidays;
  };

  return useQuery({
    queryKey: [QUERY_KEY.HOLIDAYS, calendarYear], // ๋…„๋„์— ๋”ฐ๋ผ ์ฟผ๋ฆฌํ‚ค ๋ณ€๋™
    queryFn: fetchHolidays,
    staleTime: 1000 * 60 * 60 * 24, // 1์ผ
  });
};

 

 

 

๋‹ฌ๋ ฅ์— ๊ณตํœด์ผ ์ ์šฉ

 

์ด์ œ ๋‹ฌ๋ ฅ์— ์ ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ฐ€๊ณตํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ ์šฉํ•ด์•ผ ํ•œ๋‹ค.

 

์บ˜๋ฆฐ๋” ์ปดํฌ๋„ŒํŠธ์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ƒํƒœ์™€ ๊ฐ’์„ ์ •์˜ํ•˜๊ณ  ์žˆ๋‹ค.

์ปค์Šคํ…€ ํ›…์— ํ•ด๋‹น ๋…„๋„๋ฅผ ๋ฌธ์ž์—ด๋กœ ๋„ฃ์–ด์ฃผ๊ณ  ๊ทธ ๊ฐ’์„ 'holidays'๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ๋ฐ›์•„์˜จ๋‹ค.

  const [month, setMonth] = useState(new Date()); //ํ•ด๋‹น ๋‹ฌ
  const calendarYear = month.getFullYear(); // ํ•ด๋‹น ๋‹ฌ์˜ ๋…„๋„
  const { data: holidays } = useGetHolidays(String(calendarYear)); // useQuery ์ปค์Šคํ…€ ํ›…

 

 

๊ธฐ์กด์— ๋‚˜ํƒ€๋‚ด์—ˆ๋˜ ์•ฝ์† ์ผ์ •๋“ค๊ณผ ๊ณตํœด์ผ์„ ํ•˜๋‚˜์˜ ๋ฐฐ์—ด๋กœ ํ•ฉ์ณ์„œ ๋‹ฌ๋ ฅ์— ์ „๋‹ฌํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.

  • ๊ธฐ์กด ์•ฝ์†์ธ event๋ฅผ ํŽผ์นœ๋‹ค.
  • ๊ณตํœด์ผ ๋ฐ์ดํ„ฐ์ธ holidays์„ ๊ธฐ์กด ์•ฝ์†๊ณผ ๊ฐ™์€ ์ด๋ฆ„์œผ๋กœ  ๋งคํ•‘ํ•˜์—ฌ ํŽผ์นœ๋‹ค.
  • ๋‘˜์„ ํ•˜๋‚˜์˜ ๋ฐฐ์—ด๋กœ ๋งŒ๋“ ๋‹ค.
  // ์•ฝ์† + ๊ณตํœด์ผ
  const combinedEvents: CalendarEventType[] = [
    ...(events || []),
    ...(holidays || []).map((holiday: Holidays, idx: number) => ({
      id: `holiday-${idx}`,
      title: holiday.title,
      start: holiday.date,
      end: holiday.date, // ๋‹จ์ผ ์ผ์ •
      isHoliday: true, // ์Šคํƒ€์ผ ๊ตฌ๋ถ„์šฉ
    })),
  ];

 

 

์ตœ์ข…์ ์œผ๋กœ ์บ˜๋ฆฐ๋”์— ์ ์šฉํ•˜๋ฉด ์™„๋ฃŒ!

   <DnDCalendar
        localizer={localizer}
        events={combinedEvents}
        ...
    />

 

 

๊ณต์ง€์‚ฌํ•ญ
์ตœ๊ทผ์— ์˜ฌ๋ผ์˜จ ๊ธ€
์ตœ๊ทผ์— ๋‹ฌ๋ฆฐ ๋Œ“๊ธ€
Total
Today
Yesterday
๋งํฌ
TAG
more
ยซ   2026/03   ยป
์ผ ์›” ํ™” ์ˆ˜ ๋ชฉ ๊ธˆ ํ† 
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
๊ธ€ ๋ณด๊ด€ํ•จ