「保育・教育日数マスタ」の休日データ
This commit is contained in:
77
src/保育・教育日数マスタ/main.css
Normal file
77
src/保育・教育日数マスタ/main.css
Normal file
@@ -0,0 +1,77 @@
|
||||
#legend {
|
||||
width: 100%;
|
||||
top: -24px;
|
||||
position: absolute;
|
||||
text-align: right;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
#legend .box {
|
||||
display: inline-block;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
background: #ffe3e6;
|
||||
margin-right: 4px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* カレンダーエリア */
|
||||
#calendar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-rows: repeat(4, 1fr);
|
||||
gap: 12px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.month {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
grid-template-rows: auto repeat(6, 1fr);
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.month .title {
|
||||
grid-column: 1/8;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.month .cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
#calendar:not(.readonly) .month .cell:not(.header):not(:empty) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.month .cell:not(:empty) {
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.month .sun {
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
.month .sat {
|
||||
color: #2196f3;
|
||||
}
|
||||
|
||||
.month .cell.selected {
|
||||
background: #ffe3e6;
|
||||
}
|
||||
|
||||
#calendar:not(.readonly) .month .cell:not(.header):not(.selected):not(:empty):hover {
|
||||
background: #ffe3e677;
|
||||
}
|
||||
|
||||
#calendar:not(.readonly) .month .cell.selected:hover {
|
||||
background: #ffd3d7;
|
||||
}
|
||||
312
src/保育・教育日数マスタ/main.js
Normal file
312
src/保育・教育日数マスタ/main.js
Normal file
@@ -0,0 +1,312 @@
|
||||
(() => {
|
||||
'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
|
||||
}
|
||||
}
|
||||
})()
|
||||
Reference in New Issue
Block a user