Files
kintone-attendance-system/src/保育・教育日数マスタ/main.js

312 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

(() => {
'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
}
}
})()