useTodos.js

๐Ÿ“Ž ์ƒํƒœ ์ •์˜

const todoList = ref([]);
const newTitle = ref("");

const editId = ref(null);
const editTitle = ref("");

const selectedDateObj = ref(new Date());

// ํ˜„์žฌ ๋‹ฌ๋ ฅ์—์„œ ๋ณด๊ณ  ์žˆ๋Š” ์›”(YYYY-MM)
const currentMonth = ref(toMonthStr(new Date().getFullYear(), new Date().getMonth() + 1));

// summary map: YYYY-MM-DD -> { total, remaining }
const monthRemainingMap = ref({});

๐Ÿ“Ž ์„ ํƒ ๋‚ ์งœ ๋ฌธ์ž์—ด(selectedDate)

const selectedDate = computed(() => {
  const raw = selectedDateObj.value;
  const d = raw instanceof Date ? raw : new Date(raw);
  if (isNaN(d)) return new Date().toISOString().slice(0, 10);

  const y = d.getFullYear();
  const m = String(d.getMonth() + 1).padStart(2, "0");
  const day = String(d.getDate()).padStart(2, "0");
  return `${y}-${m}-${day}`;
});

๐Ÿ“Ž ์„ ํƒ ๋‚ ์งœ remaining ๊ณ„์‚ฐ(remainingForSelectedDate)

const remainingForSelectedDate = computed(() => {
  return monthRemainingMap.value[selectedDate.value]?.remaining ?? 0;
});

๐Ÿ“Ž ์บ˜๋ฆฐ๋” attribute ์ƒ์„ฑ(calendarAttrs)

const calendarAttrs = computed(() => {
  const attrs = [
    {
      key: "today",
      dates: new Date(todayStr + "T00:00:00"),
      highlight: { outline: true },
    },
    {
      key: "selected",
      dates:
        selectedDateObj.value instanceof Date
          ? selectedDateObj.value
          : new Date(selectedDateObj.value),
      highlight: true,
    },
  ];

  for (const [dateStr, v] of Object.entries(monthRemainingMap.value)) {
    const total = v?.total ?? 0;
    const remaining = v?.remaining ?? 0;
    if (total <= 0) continue;

    attrs.push({
      key: `rem-${dateStr}`,
      dates: new Date(dateStr + "T00:00:00"),
      dot: { color: remaining === 0 ? "green" : "red" },
    });
  }

  return attrs;
});

๐Ÿ“Ž ๋‚ ์งœ๋ณ„ Todo ์กฐํšŒ(loadTodoList)

async function loadTodoList() {
  if (!tokenRef.value) {
    todoList.value = [];
    return;
  }

  const res = await fetchTodosApi({
    date: selectedDate.value,
    authHeader: authHeaderFn(),
  });

  if (res?.__unauthorized) return onUnauthorized?.();
  todoList.value = Array.isArray(res) ? res : [];
}

๐Ÿ“Ž ์›” summary ์กฐํšŒ(loadMonthSummary)

async function loadMonthSummary(monthStr) {
  if (!tokenRef.value) return;

  currentMonth.value = monthStr;

  const data = await fetchMonthSummaryApi({
    monthStr,
    authHeader: authHeaderFn(),
  });

  if (data?.__unauthorized) return onUnauthorized?.();

  const map = {};
  (Array.isArray(data) ? data : []).forEach((row) => {
    map[row.date] = { total: row.total, remaining: row.remaining };
  });

  monthRemainingMap.value = map;
}

๐Ÿ“Ž ์ถ”๊ฐ€(addTodo)

async function addTodo() {
  if (!tokenRef.value) return;
  const title = newTitle.value.trim();
  if (!title) return;

  const res = await addTodoApi({
    title,
    date: selectedDate.value,
    authHeader: authHeaderFn(),
  });

  if (res?.__unauthorized) return onUnauthorized?.();

  newTitle.value = "";
  await loadTodoList();
  await loadMonthSummary(currentMonth.value);
}

๐Ÿ“Ž ์™„๋ฃŒ(toggleTodo)

async function toggleTodo(todo) {
  const res = await toggleTodoApi({
    id: todo._id,
    done: !todo.done,
    authHeader: authHeaderFn(),
  });

  if (res?.__unauthorized) return onUnauthorized?.();

  await loadTodoList();
  await loadMonthSummary(currentMonth.value);
}

๐Ÿ“Ž ์ˆ˜์ •(startEdit / cancelEdit / saveEdit)

๐Ÿ“Ž ์™„๋ฃŒ(toggleTodo)