312 lines
11 KiB
JavaScript
312 lines
11 KiB
JavaScript
(() => {
|
||
'use strict'
|
||
let _CALENDAR
|
||
|
||
// レコード詳細/新規作成/編集画面表示時にカレンダーを初期化
|
||
kintone.events.on(
|
||
['app.record.detail.show', 'app.record.create.show', 'app.record.edit.show'],
|
||
(event) => {
|
||
kintone.app.record.setFieldShown('休日の生データ', false);
|
||
initCalendarArea(
|
||
event.record,
|
||
getHolidayMap(event.record),
|
||
event.type === 'app.record.detail.show'
|
||
)
|
||
return event
|
||
}
|
||
)
|
||
|
||
// 年度フィールド変更時に各月の休日をクリアしてカレンダーを再描画
|
||
kintone.events.on(
|
||
[
|
||
'app.record.create.change.年度',
|
||
'app.record.edit.change.年度',
|
||
'app.record.index.edit.change.年度'
|
||
],
|
||
(event) => {
|
||
forEachRawFields((field) => {
|
||
event.record[field].value = ''
|
||
})
|
||
if (event.type !== 'app.record.index.edit.change.年度') {
|
||
initCalendarArea(event.record)
|
||
}
|
||
return event
|
||
}
|
||
)
|
||
|
||
// 一覧編集画面では各月の休日フィールドを編集不可にする
|
||
kintone.events.on(
|
||
['app.record.index.edit.show'],
|
||
(event) => {
|
||
forEachRawFields((field) => {
|
||
event.record[field].disabled = true
|
||
})
|
||
return event
|
||
}
|
||
)
|
||
|
||
// レコード保存時にカレンダーで選択した休日を各月フィールドへ反映
|
||
kintone.events.on(
|
||
['app.record.create.submit', 'app.record.edit.submit'],
|
||
(event) => {
|
||
const resultMap = _CALENDAR.getSelectedDatesMap()
|
||
forEachRawFields((field, i) => {
|
||
event.record[field].value = resultMap[i].join(',')
|
||
})
|
||
return event
|
||
}
|
||
)
|
||
|
||
/* --------------------------------------------------
|
||
* 初期化・データ準備
|
||
* -------------------------------------------------- */
|
||
// カレンダー表示エリアを初期化
|
||
function initCalendarArea(record, map = {}, readonly = false) {
|
||
const calendarAreaEl = kintone.app.record.getSpaceElement('calendar-area')
|
||
calendarAreaEl.innerHTML = ''
|
||
const year = record.年度.value
|
||
if (isYearString(year)) {
|
||
_CALENDAR = createCalendar(Number(year), map, readonly)
|
||
calendarAreaEl.appendChild(_CALENDAR.fragment)
|
||
}
|
||
}
|
||
|
||
// 既存レコードに保存されている休日を取得
|
||
function getHolidayMap(record) {
|
||
const res = {}
|
||
forEachRawFields((field, i) => {
|
||
const str = record[field].value
|
||
if (!str) {
|
||
return
|
||
}
|
||
res[i] = new Set(str.split(','))
|
||
})
|
||
return res
|
||
}
|
||
|
||
/* --------------------------------------------------
|
||
* ユーティリティ
|
||
* -------------------------------------------------- */
|
||
// 各月フィールドをループ
|
||
const forEachRawFields = (callback) => {
|
||
for (let i = 1; i <= 12; i++) {
|
||
callback(`休日_${i}月`, i)
|
||
}
|
||
}
|
||
|
||
// ゼロパディング
|
||
const padZero = (n) => (n < 10 ? '0' + n : n)
|
||
|
||
// Date → yyyy-MM-dd
|
||
const toYmd = (d) => `${d.getFullYear()}-${padZero(d.getMonth() + 1)}-${padZero(d.getDate())}`
|
||
|
||
// 土日判定
|
||
const isWeekend = (d) => d.getDay() === 0 || d.getDay() === 6
|
||
|
||
// 年度文字列の妥当性チェック
|
||
function isYearString(str) {
|
||
// 文字列かつ空でない
|
||
if (typeof str !== 'string' || str.length === 0) {
|
||
return false
|
||
}
|
||
|
||
// 数字のみ
|
||
if (!/^\d+$/.test(str)) {
|
||
return false
|
||
}
|
||
|
||
// 4桁
|
||
if (str.length !== 4) {
|
||
return false
|
||
}
|
||
|
||
// 1900-3000 の範囲
|
||
const year = parseInt(str, 10)
|
||
return year >= 1900 && year <= 3000
|
||
}
|
||
|
||
/* --------------------------------------------------
|
||
* カレンダー生成
|
||
* -------------------------------------------------- */
|
||
const createCalendar = (year, highlightMap, readonly) => {
|
||
const fragment = document.createDocumentFragment()
|
||
|
||
/* ----- 図例 ----- */
|
||
const legend = document.createElement('div')
|
||
legend.id = 'legend'
|
||
legend.innerHTML = '<span class="box"></span>休日'
|
||
fragment.appendChild(legend)
|
||
|
||
/* ----- カレンダーコンテナ ----- */
|
||
const calendar = document.createElement('div')
|
||
calendar.id = 'calendar'
|
||
if (readonly) {
|
||
calendar.classList.add('readonly')
|
||
}
|
||
fragment.appendChild(calendar)
|
||
|
||
/* ----- 内部状態 ----- */
|
||
const selectedSet = new Set()
|
||
const months = buildMonths(year)
|
||
months.forEach((m) => {
|
||
calendar.appendChild(buildMonth(m))
|
||
})
|
||
|
||
/* ---------- 月情報生成 ---------- */
|
||
function buildMonths(year) {
|
||
return Array.from({ length: 12 }, (_, i) => {
|
||
const monthIndex = 3 + i // 0 基準月で 4 月始まり
|
||
const date = new Date(year, monthIndex, 1)
|
||
return {
|
||
displayMonth: date.getMonth() + 1,
|
||
year: date.getFullYear(),
|
||
month: date.getMonth(),
|
||
label: `${date.getFullYear()}年${date.getMonth() + 1}月`
|
||
}
|
||
})
|
||
}
|
||
|
||
/* ---------- 月単位カレンダー生成 ---------- */
|
||
function buildMonth({ displayMonth, year, month, label }) {
|
||
const monthBox = document.createElement('div')
|
||
monthBox.className = 'month'
|
||
|
||
buildTitle(monthBox, label)
|
||
buildHeader(monthBox)
|
||
|
||
const first = new Date(year, month, 1)
|
||
buildPadBlocksStart(first, monthBox)
|
||
buildDateBlocks(year, month, displayMonth, monthBox)
|
||
buildPadBlocksEnd(monthBox)
|
||
|
||
return monthBox
|
||
}
|
||
|
||
function buildTitle(monthBox, label) {
|
||
const title = document.createElement('div')
|
||
title.className = 'title'
|
||
title.textContent = label
|
||
monthBox.appendChild(title)
|
||
}
|
||
|
||
function buildHeader(monthBox) {
|
||
['日', '月', '火', '水', '木', '金', '土'].forEach((day, i) => {
|
||
const cell = document.createElement('div')
|
||
cell.className = 'cell header'
|
||
addWeekendClass(cell, i)
|
||
cell.textContent = day
|
||
monthBox.appendChild(cell)
|
||
})
|
||
}
|
||
|
||
function addWeekendClass(cell, week) {
|
||
if (week === 0) {
|
||
cell.classList.add('sun')
|
||
} else if (week === 6) {
|
||
cell.classList.add('sat')
|
||
}
|
||
}
|
||
|
||
function buildPadBlocksStart(first, monthBox) {
|
||
const startDayInWeek = first.getDay()
|
||
for (let i = 0; i < startDayInWeek; i++) {
|
||
const cell = document.createElement('div')
|
||
cell.className = 'cell'
|
||
monthBox.appendChild(cell)
|
||
}
|
||
}
|
||
|
||
function buildDateBlocks(year, month, displayMonth, monthBox) {
|
||
const last = new Date(year, month + 1, 0)
|
||
const highlights = highlightMap[displayMonth] || null
|
||
|
||
for (let day = 1; day <= last.getDate(); day++) {
|
||
const dateObj = new Date(year, month, day)
|
||
const ymd = toYmd(dateObj)
|
||
const cell = document.createElement('div')
|
||
cell.className = 'cell'
|
||
cell.textContent = day
|
||
cell.dataset.date = ymd
|
||
|
||
addWeekendClass(cell, dateObj.getDay())
|
||
|
||
// 初期選択判定
|
||
let shouldSelect = false
|
||
if (highlights === null) {
|
||
shouldSelect = isWeekend(dateObj)
|
||
} else if (highlights instanceof Set) {
|
||
shouldSelect = highlights.has(ymd)
|
||
}
|
||
|
||
if (shouldSelect) {
|
||
cell.classList.add('selected')
|
||
selectedSet.add(ymd)
|
||
}
|
||
|
||
// 編集可モードのみクリックイベントを追加
|
||
if (!readonly) {
|
||
cell.addEventListener('click', () => {
|
||
cell.classList.toggle('selected')
|
||
if (cell.classList.contains('selected')) {
|
||
selectedSet.add(ymd)
|
||
} else {
|
||
selectedSet.delete(ymd)
|
||
}
|
||
})
|
||
}
|
||
|
||
monthBox.appendChild(cell)
|
||
}
|
||
}
|
||
|
||
function buildPadBlocksEnd(monthBox) {
|
||
// 7×7 マスに揃えるための埋め草
|
||
while (monthBox.children.length < 49) {
|
||
const cell = document.createElement('div')
|
||
cell.className = 'cell'
|
||
monthBox.appendChild(cell)
|
||
}
|
||
}
|
||
|
||
/* ---------- 選択済み日付取得 ---------- */
|
||
function getSelectedDatesMap() {
|
||
const res = {}
|
||
months.forEach(({ month }) => {
|
||
res[month + 1] = []
|
||
})
|
||
|
||
// 選択済み日付を振り分け
|
||
selectedSet.forEach((ymd) => {
|
||
const d = new Date(ymd)
|
||
const displayMonth = d.getMonth() + 1
|
||
res[displayMonth].push(ymd)
|
||
})
|
||
|
||
// 選択が無い月は全土日を自動追加
|
||
Object.keys(res).forEach((monthKey) => {
|
||
if (res[monthKey].length === 0) {
|
||
const monthInfo = months.find((m) => m.displayMonth == monthKey)
|
||
if (monthInfo) {
|
||
const year = monthInfo.year
|
||
const month = monthInfo.month
|
||
const lastDay = new Date(year, month + 1, 0).getDate()
|
||
|
||
for (let day = 1; day <= lastDay; day++) {
|
||
const dateObj = new Date(year, month, day)
|
||
if (isWeekend(dateObj)) {
|
||
res[monthKey].push(toYmd(dateObj))
|
||
}
|
||
}
|
||
}
|
||
}
|
||
})
|
||
|
||
return res
|
||
}
|
||
|
||
return {
|
||
fragment,
|
||
getSelectedDatesMap
|
||
}
|
||
}
|
||
})() |