webixでスケジューラ機能を実装(その2)No.069
前回(No.068)では、Webixライブラリでスケジューラを作成する概要を説明しましたが、今回は、実際に実装したイメージを紹介します。
まだ、正式運用には改造や機能追加が必要ですが、基本機能の実装をしましたので紹介します。
このスケジュールは、以下の条件(要件)で動作させることを想定して実装しています。
(1)複数ユーザが、スケジュールを共有(閲覧)したり、プライベート(非公開)のスケジュールを運用できる。個人のスケジュールには、公開用と非公開用を持ち、グループで共有するグループスケジュールも実装します。この機能を実現するためには、本来のスケジュールライブラリのカレンダ機能を一部拡張する必要があります。
(2)使用するユーザ情報は、システムで管理し、このスケジューラ内での追加・変更・削除機能(カレンダ機能)は使用しない。
webixのスケジューラは、単独でも動作するように、機能内でユーザを追加したり、変更したり、削除する機能(カレンダ機能)が実装されていますが、通常のシステムでは、ユーザは、別に管理されているので、その機能で運用できるようにします。(システム内のユーザに対し、スケジュール機能を使う・使わない、使う場合に表示する名称や色などを管理する機能は実装が必要です。:カレンダ機能を使用しないので)
(3)サーバ側のスケジュール機能はPHP言語で実装する。(汎用的なレンタルサーバで運用することを考慮して、PHP言語で実装します)
サンプルで作成した画面イメージです。
赤い枠部分が基本機能と異なる点です。
webixライブラリは、機能拡張できる実装がされているので、マニュアルやサンプルソースを参考にすると、機能改造が可能です。
いろいろ、英文を読んで、トライが必要ですが、どうにかなります。(有償ライセンスを購入していれば、サポート部門にメールなどで実装方法の問い合わせも可能です)
今回のライブラリ(スケジューラ)は、有償ライセンスを使用する必要があるので、問い合わせも可能です。(Pro版で使用できるmultiselect機能も使っています)
リストボックスにチェックボックスが追加された機能で、複数から必要な候補を選択できます。
今回、どのスケジュールを画面に表示するかを選択する機能の実装で使用しています。
複数カレンダ(ユーザ)を表示するモードにすると、作成するスケジュールをどのユーザ(公開・非公開・グループ01)で作成するかも選択できます。
スケジュールを作成するときに付与する色情報は、ユーザに割り付けた色がデフォルト色になりますが、作成するイベント単位に色を指定することも可能です。このあたりは、運用で定義するルールですが、あまり色を使いすぎるとわかりにくくなりますね。
作成したUI(Javascript)のソースです。SH0010_Scheduler_lists.php
日本語化の辞書情報の定義も含めています。
まだ、サーバ側機能を完全には実装していないので、一部、情報をハードコーディングしています。(ユーザ情報の候補値など)
<?php
//======================================================================
//File Name : SH0010_Scheduler_lists.php
//Encoding : UTF-8
//Creation Date : 2024-10-04
//
//Copyright © 2024 sunsunfarm. All rights reserved.
//
//This source code or any portion thereof must not be
//reproduced or used in any manner whatsoever.
//
//======================================================================
//
//https://yamasanfarm.sakuraweb.com/webix02/view/SH0010/SH0010_Scheduler_lists.php?userid=admin
$VER_INFO ="V01L01";
$TITLE_INFO = "scheduler";
$myfilename = basename(__FILE__); //自分自身のファイル名取得
define('ROOT_PATH','/home/sunsun/www/webix02'); //ソースを保存しているパス(動作環境に応じて記述する必要あり)
define('SUB_FOLDER','/webix02'); //サブフォルダを指定したURL
$userid = '';
if(isset($_GET['userid'])){
$userid = $_GET['userid'];
}
$logheader = 'userid='.$userid.', '.$myfilename.':';//ログ出力時のヘッダー情報(自ファイル名,ログインIDを付与)
error_log($logheader.' start '.$myfilename);
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="/webix-pro1021/webix.css" type="text/css" charset="utf-8">
<script src="/webix-pro1021/webix.js"></script>
<link href="/webix-pro1021/skins/compact.css?<?php echo date('Ymd-H'); ?>" rel="stylesheet" type="text/css">
<link rel="icon" href="<?php echo SUB_FOLDER; ?>/image/webix_64.ico">
<script src="<?php echo SUB_FOLDER; ?>/commonlib/moment-with-locales.js"></script>
<script type="text/javascript" src="/components/scheduler/scheduler.js"></script>
<link rel="stylesheet" href="/components/scheduler/scheduler.css" type="text/css">
<script src="<?php echo SUB_FOLDER; ?>/commonlib/webix_common_lib.js"></script>
<link rel="stylesheet" href="/webix-pro1021/css/materialdesignicons.min.css" type="text/css" charset="utf-8">
<title><?php echo $TITLE_INFO.' ('.$VER_INFO.')' ?></title>
<style>
</style>
</head>
<body>
<script type="text/javascript" charset="utf-8">
<?php
include_once('../../commonlib/CM0010_goto_menu_action.php');
include_once('../../commonlib/CM0030_sendprm_set_request.php'); //送信パラメータ準備関数(in:session_info,access_key out:send_prm)
?>
//var my_local_session = webix.storage.local.get('login');
var my_local_session = {};
my_local_session.userid = "admin";
let today = new Date(); //表示の初期値は、当日をセット
// set default locale for date formatting
webix.i18n.setLocale("ja-JP");
webix.ui.fullScreen();
//カレンダー辞書
webix.i18n.locales["ja-JP"]={
dateFormat:"%Y/%m/%d",
timeFormat:"%H:%i",
longDateFormat:"%Y年%m月%d日",
fullDateFormat:"%Y/%m/%d %H:%i",
calendar:{
monthFull:["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"],
monthShort:[ "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"],
dayShort:["日", "月", "火", "水", "木", "金", "土"],
hours: "時刻", minutes: "分", done: "決定", clear: "削除", today: "今日",
},
price:"¥{obj}",
priceSettings:{
groupSize:3,
groupDelimiter:",",
decimalDelimiter:"",
decimalSize:0
},
};
webix.i18n.calendar.hours = "時刻";
webix.i18n.calendar.dateFormat = "%Y/%m/%d";
webix.i18n.setLocale();
//スケジューラja用辞書
const jaLocale = {
//navigation
Week: "週",
Day: "日",
Month: "月",
Agenda: "アジェンダ",
Timeline: "時系列",
Units: "Units",
Today: "本日",
Create: "作成",
Next: "次",
Previous: "前",
"Next day": "翌日",
"Previous day": "前日",
"Next week": "翌週",
"Previous week": "前週",
"Next month": "翌月",
"Previous month": "前月",
// managing calendars
"Add calendar": "カレンダー追加",
"Do you really want to remove this calendar?":"カレンダーを削除してもいいですか?",
"Edit calendar": "カレンダーを編集しますか?",
Delete: "削除",
Save: "保存",
Title: "タイトル",
Color: "色",
Active: "有効",
Settings: "設定",
"(no title)": "(無題)",
"Inactive calendar": "カレンダ無効化",
// modes
"No Events": "イベント無し",
"All Day": "全日",
"more": "さらに",
"Expand all-day events": "Expand all-day events",
"Collapse all-day events": "Collapse all-day events",
// info and form
"The event will be deleted permanently, are you sure?": "イベントを削除しますが、よろしいですか?",
"Done": "完了",
"Delete event": "イベント削除",
Close: "閉じる",
Edit: "編集",
"(No title)": "(無題)",
Event: "イベント",
Start: "開始",
End: "終了",
Calendar: "カレンダ",
Notes: "メモ",
from: "開始: ",
to: "終了: ",
"Edit event": "イベント編集",
// recurring
never: "無し",
none: "無し",
daily: "毎日",
day: "日",
days: "days",
every: "every",
weekly: "毎週",
week: "週",
weeks: "weeks",
each: "each",
monthly: "monthly",
month: "month",
months: "months",
yearly: "yearly",
Year: "年",
year: "年",
years: "毎年",
Repeat: "繰返し",
"End repeat": "End repeat",
"Repeats each": "Repeats each",
till: "till",
times: "times",
"weekly, every": "weekly, every",
"monthly, every": "monthly, every",
"yearly, every": "yearly, every",
"every working day": "every working day",
custom: "custom",
Every: "Every",
on: "on",
of: "",
"after several occurrences": "after several occurrences",
date: "date",
"week on": "week on",
"Change recurring pattern": "Change recurring pattern",
"Discard changes?": "Discard changes?",
// menus for edit and delete
"All events": "All events",
"This event": "This event",
"This event and the following": "This event and the following",
Cancel: "キャンセル",
Apply: "適用",
"Edit recurring event": "Edit recurring event",
"Timeline scale": "Timeline scale",
Section: "Section",
"Show more": "Show more",
};
var SH0010_multiselect_options=[
{ id:1, value:"admin 公開" },
{ id:2, value:"admin private" },
{ id:10000, value:"グループ01 公開" },
];
var SH0010_multiselect_init_v = "1,2,10000";
function SH0010_get_multiselect_options(){
//{ id:1, value:"admin private" },
//{ id:2, value:"admin 公開" },
//{ id:1000, value:"Group01 公開" },
//{ id:1001, value:"Group01 公開" }
//"1,2";
var access_key = Get_AccessKey();
var send_prm = Prepare_send_prm(my_local_session,access_key);
send_prm.fl_userid = my_local_session.userid;
send_prm.fl_group_filter = 1;
var xhr =webix.ajax().sync().get("<?php echo SUB_FOLDER; ?>/rest_api/SH0020/SH0021_calendar_options_lists.php",send_prm);
var resp = JSON.parse(xhr.responseText);
if(resp.resp =="ok"){
SH0010_multiselect_options = resp.options;
SH0010_multiselect_init_v = resp.init_v;
}
else{
webix.message({type:"error",text:"SH0010_get_multiselect_optionsでエラーが発生しました。code="+resp.error_code});
}
}
//サーバへのリクエストインタフェース
class MyBackend extends scheduler.services.Backend {
events(optional){
var access_key = Get_AccessKey();
var send_prm = Prepare_send_prm(my_local_session,access_key);
if(optional){
send_prm.from = optional.from;
send_prm.to = optional.to;
}
var select_calendar_filter = $$("SH0010_select_calendar_filter").getValue();
send_prm.select_calendar_filter = select_calendar_filter;
var xhr =webix.ajax().sync().get("<?php echo SUB_FOLDER; ?>/rest_api/SH0010/SH0010_scheduler_DB_selects.php",send_prm);
var resp = JSON.parse(xhr.responseText);
if(resp.resp =="ok"){
return webix.promise.resolve(resp.val_array);
}
else{
webix.message({type:"error",text:"検索でエラーが発生しました。code="+resp.error_code});
return webix.promise.resolve([]);
}
};
//Adds a new event to the server.
//Returns: a promise that resolves with an object containing ID of the new event.
//Request:
//POST http://localhost:3000/events
//Form Data
//recurring: FREQ=YEARLY;BYMONTH=1;BYMONTHDAY=1
//text: Meeting
//start_date: 2018-05-21 20:00:00
//end_date: 2018-05-21 21:00:00
//all_day: 0
//calendar: 1
//color: #997CEB
//details: Meeting with Dave at Starbucks
//sections: 1
//units: 3
//You can read more about the recurring string rules in the iCal specification.
//Response:
//The server returns an object containing ID of the new event.
//{id: "mY7VoF4uLsxHaJqf"}
addEvent(obj){
var formData = new FormData();
var access_key = Get_AccessKey();
formData.append("accesskey",access_key);
formData.append("userid",my_local_session.userid);
formData.append("obj",JSON.stringify(obj));
var xhr =webix.ajax().sync().post("<?php echo SUB_FOLDER; ?>/rest_api/SH0010/SH0011_scheduler_add_actions.php",formData);
var resp = JSON.parse(xhr.responseText);
if(resp.resp =="ok"){
var rs ={};
var event_id = Number(resp.event_id);
rs.id = event_id ;
return webix.promise.resolve(rs);
}
else{
return webix.promise.resolve(false);
}
};
//Updating a specified event.
//
//Parameters:
//
//id (string, number) - event ID;
//obj (object) - new data;
//mode (string) - the name of the mode in which the event will be updated:
//"this" - pass this if you want to update the occurrence on this date;
//"all" (a signal to remove all subevents). Pass all if you want to update all occurrences;
//"next" (a signal to remove all old subevents after this date). Pass next if you want to update occurrences starting from a certain date;
//date (Date) - optional. The date used for updating in "next" mode (also only for recurring events).
//Returns: a promise.
//
//Request:
//
//PUT http://localhost:3000/events/mY7VoF4uLsxHaJqf
//Form Data
// non-recurring
//"start_date":"2020-10-10 11:35:00","end_date":"2020-10-10 14:35:00"
//"mode": "all"
//
// for recurring
//"recurring":"FREQ=DAILY;INTERVAL=1;UNTIL=20201009T000000Z"
//"recurring_update_date": some date
//"recurring_update_mode": "next"
//"mode": next
//"date": 2020-10-09 00:00:00
//You can read more about the recurring string rules in the iCal specification.
//
//removeEvent(id)
//Removes a specified event.
//
//Parameters:
//
//id (string, number) - event ID.
//Returns: a promise.
updateEvent(id, obj, mode , date){
var formData = new FormData();
var access_key = Get_AccessKey();
formData.append("accesskey",access_key);
formData.append("userid",my_local_session.userid);
formData.append("event_id",id);
formData.append("obj",JSON.stringify(obj));
var xhr =webix.ajax().sync().post("<?php echo SUB_FOLDER; ?>/rest_api/SH0010/SH0012_scheduler_update_actions.php",formData);
var resp = JSON.parse(xhr.responseText);
if(resp.resp =="ok"){
return webix.promise.resolve(true);;
}
else{
return webix.promise.resolve(false);
}
};
//Removes a specified event.
//
//Parameters:
//
//id (string, number) - event ID.
//Returns: a promise.
//
//Request:
//
//DELETE http://localhost:3000/events/mY7VoF4uLsxHaJqf
removeEvent(id){
var formData = new FormData();
var access_key = Get_AccessKey();
formData.append("accesskey",access_key);
formData.append("userid",my_local_session.userid);
formData.append("event_id",id);
var xhr =webix.ajax().sync().post("<?php echo SUB_FOLDER; ?>/rest_api/SH0010/SH0013_scheduler_remove_actions.php",formData);
var resp = JSON.parse(xhr.responseText);
if(resp.resp =="ok"){
return webix.promise.resolve(true);
}
else{
return webix.promise.resolve(false);
}
};
//Is called when Scheduler is switched to the Timeline mode.
//
//Returns: a promise that resolves with an array of all sections.
//
//Request:
//
//GET http://localhost:3000/sections
//Response:
//
//The server returns an array of the sections data objects.
//
//[
// {"text":"Section 1","id":"1"},
// {"text":"Section 2","id":"2"},
// // other sections
//]
sections(){
//未実装
return webix.promise.resolve([]);
};
//Is called when Scheduler is switched to the Units mode.
//
//Returns: a promise that resolves with an array of all units.
//
//Request:
//
//GET http://localhost:3000/units
//Response:
//
//The server returns an array of the units data objects.
//
//[
// {"id":"1"," value":"Sports"},
// {"id":"2", "value":"Social activities"},
// {"id":"3","value":"Celebrations"}
//]
units(){
//未実装
return webix.promise.resolve([]);
};
//カレンダ検索
calendars(){
var access_key = Get_AccessKey();
var send_prm = Prepare_send_prm(my_local_session,access_key);
send_prm.event = event;
var xhr =webix.ajax().sync().get("<?php echo SUB_FOLDER; ?>/rest_api/SH0020/SH0020_calendar_DB_selects.php",send_prm);
var resp = JSON.parse(xhr.responseText);
if(resp.resp =="ok"){
return webix.promise.resolve(resp.val_array);
}
else{
webix.message({type:"error",text:"検索でエラーが発生しました。code="+resp.error_code});
return [];
}
};
//カレンダ追加
addCalendar(obj){
//未実装
var rs ={};
var event_id = 0;
rs.id = event_id ;
return webix.promise.resolve(rs);
};
//カレンダ更新
updateCalendar(id, obj){
//未実装
return webix.promise.resolve(true);
};
//カレンダ削除
removeCalendar(id){
//未実装
return webix.promise.resolve(true);
}
}
class NavBar extends scheduler.views["bars/nav"] {
config() {
const curlang = this.app.getService("locale").getLang();
const ui = super.config();
if (curlang === "ja") {
ui.optionWidth += 30;
if (ui.options.length < 5) ui.width += 30 * ui.options.length;
}
return ui;
}
}
class CustomSideIndex extends scheduler.views["side"]{
init() {
// default logic
super.init();
// get instance of the component with "add" localId
const add = this.$$("add");
add.disable();
const calendars = this.$$("calendars");
calendars.disable();
};
config() {
let ui = super.config();
return ui
}
}
webix.ready(function() {
// use custom scrolls, optional
webix.CustomScroll.init();
// set custom translations for scheduler
scheduler.locales.ja = jaLocale;
const fm = webix.ui(
{view:"form",
//scroll:false,
//width:1200,
elements:[
{ margin:5, cols:[
{ view:"multiselect", label:"表示対象", labelWidth:100,inputWidth: 400,id:"SH0010_select_calendar_filter",
options:SH0010_multiselect_options,
value: SH0010_multiselect_init_v,
on:{
onChange: function(newValue, oldValue, config){
// config is {yourProperty: "yourValue"}
$$("scheduler").clearAll();
$$("scheduler").$app.refresh();
webix.message({type:"success",text:"スケジュールを再描画しました。"});
}
}
},
{ view:"button", label:"再表示" , width:100,type:"form" ,id:"SH0010_research_btn",
click:function(){
$$("scheduler").clearAll();
$$("scheduler").$app.refresh();
webix.message({type:"success",text:"スケジュールを再描画しました。"});
}
},
{},
{ view:"button", label:"メニュー", width:100 ,id:"SH0010_close_btn", css:"menu",
click:function(){
//Goto_Menu();
}
}
]
},
{view: "scheduler",
id: "scheduler",
date: today ,
dynamic: "month", //期間指定API使用
//calendars: false, //カレンダ機能非表示
recurring: false, // hides all the recurring events
url: "https://yamasanfarm.sakuraweb.com/webix02/rest_api/SH0010/calendars.php?info=",
override: new Map([
[scheduler.services.Backend, MyBackend],
[scheduler.views["bars/nav"], NavBar],
[scheduler.views["side"], CustomSideIndex],
]),
locale: { lang: "ja" },
on: {
onChange: function(date) {
if(date === tomorrow) {
// do smth
}
},
onInit: app => {
const state = app.getState();
// outputs mode and selected event
//state.$observe("mode", v => webix.message(`You're in the "${v}" display mode!`));
//state.$observe("selected", v => {
// if(v !== null) webix.message(`The ID of the selected event is ${v.id}`)
//});
}
}
}
]
}
);
});
</script>
</body>
</html>
サーバへアクセスする部分は、機能拡張してPHPのソースにアクセスするようにコーディングしています。(Ajax通信で結果をJSON形式で取得)
オンラインマニュアルを見ながら、NODEJなどのサンプルソースを参照してPHP側のソースを作成しました。
以下は、スケジュール情報を指定されたユーザと期間をパラメータにeventテーブルを検索するPHPのソースです。
SH0010_scheduler_DB_selects.php
<?php
//======================================================================
//File Name : SH0010_scheduler_DB_selects.php
//Encoding : UTF-8
//Creation Date : 2024-10-04
//
//Copyright © 2024 sunsunfarm. All rights reserved.
//
//This source code or any portion thereof must not be
//reproduced or used in any manner whatsoever.
//======================================================================
header("Content-Type: text/javascript; charset=utf-8");
if($_SERVER["REQUEST_METHOD"] != "GET"){
header("HTTP/1.0 404 Not Found");
return;
}
//検索条件用パラメータ(初期値)
$userid ='';
$accesskey =0;
$error_flag = -1;
//GET情報からパラメータ取得
if(isset($_GET['accesskey'])){
if(is_numeric($_GET['accesskey'])){
$accesskey =$_GET['accesskey'];
}
}
else{
$error_flag = -1;
$result_code = "ng";
$error_code = -1;
$json_data = json_encode(compact("result_code","error_code"),JSON_UNESCAPED_UNICODE);
echo $json_data;
exit;
}
if(isset($_GET['userid'])){
$userid = $_GET['userid'];
}
else{
$error_flag = -1;
$result_code = "ng";
$error_code = -2;
$json_data = json_encode(compact("result_code","error_code"),JSON_UNESCAPED_UNICODE);
echo $json_data;
exit;
}
$from = '';
if(isset($_GET['from'])){
$from = $_GET['from'];
}
$to = '';
if(isset($_GET['to'])){
$to = $_GET['to'];
}
$select_calendar_filter = '';
if(isset($_GET['select_calendar_filter'])){
$select_calendar_filter = $_GET['select_calendar_filter'];
}
$myfilename = basename(__FILE__); //自分自身のファイル名取得
$logheader = 'userid='.$userid.', '.$myfilename.':';//ログ出力時のヘッダー情報(自ファイル名,ログインIDを付与)
include('../../commonlib/svr_common_lib_v2.php'); //
$config_obj = get_config_obj();
//アクセスキーチェック
if(Chk_AccessKey($accesskey)){
$error_flag = 1;
}
else{
$result_code = "ng";
$error_code = -3;
$json_data = json_encode(compact("result_code","error_code"),JSON_UNESCAPED_UNICODE);
echo $json_data;
exit;
}
//検索用SQL文作成
function create_select_sql($from,$to,$select_calendar_filter)
{
$select_sql1 = " SELECT id,text,start_date,end_date,color,calendar,details,all_day,recurring,units from event";
if($from!='' || $to!=''||$select_calendar_filter!=''){
$where_str_array_A = array();
if($from!=''){
$where_str_array_A[] = "end_date >= '".$from."'";
}
if($to!=''){
$where_str_array_A[] = "start_date <= '".$to."'";
}
if($select_calendar_filter!=''){
$where_str_array_A[] = "calendar in (".$select_calendar_filter.")";
}
$where_str_A = implode(" and ",$where_str_array_A);
$select_sql1 = $select_sql1." where ".$where_str_A;
}
return $select_sql1;
}
//
//メインルーチン
//
error_log($logheader.' SH0010_scheduler_DB_selects start from:'.$from.' to:'.$to.' select_calendar_filter='.$select_calendar_filter);
if($error_flag == -1){
//正しい遷移でない場合は、強制的にログイン画面に遷移
header("HTTP/1.0 404 Not Found");
return;
}
//データベース接続する(MariaDB)
$dbh = mariadb_connect($config_obj,'app01','webix_webix');
if($dbh == false){
//エラー応答
error_log($logheader.' DB connect error');
$resp = "ng";
$error_code = -2;
$json_data = json_encode(compact("resp","error_code"),JSON_UNESCAPED_UNICODE);
echo $json_data;
exit;
}
$select_sql = create_select_sql($from,$to,$select_calendar_filter);
error_log($logheader.' SQL ='.$select_sql);
//SQL文の実行
$stmt = $dbh->query($select_sql );
$val_array = array();
try {
$val_array = $stmt->fetchAll(PDO::FETCH_ASSOC); //フィールド名だけで保存(index情報なし)
$resp = "ok";
$error_code = 0;
}catch (Exception $e) {
error_log($logheader.' 捕捉した例外: '.$e->getMessage()); //例外発生時の処理(エラー情報をログに格納してエラー応答)
$resp = "ng";
$error_code = -1;
$json_data = json_encode(compact("resp","error_code"),JSON_UNESCAPED_UNICODE);
echo $json_data;
}
$stmt = null;
$dbh = null;
$json_data = json_encode(compact("resp","error_code","val_array"),JSON_UNESCAPED_UNICODE);
echo $json_data;
?>
eventテーブル定義は、以下のとおりです。
CREATE TABLE event
(
id int(11) auto_increment primary key,
text varchar(255) not null,
start_date varchar(255) not null,
end_date varchar(255) not null,
color varchar(7) default '' not null,
calendar int not null,
details text not null,
all_day int default 0 not null,
recurring varchar(255) default '' not null,
units varchar(255) not null,
`created_userid` varchar(16) NOT NULL DEFAULT 'admin' COMMENT '作成ユーザ',
`updated_userid` varchar(16) NOT NULL DEFAULT 'admin' COMMENT '更新ユーザ',
`created_on` datetime DEFAULT NULL COMMENT '作成日時',
`updated_on` datetime DEFAULT NULL COMMENT '更新日時'
)ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='イベント';
サーバ側で実装が必要な機能は、マニュアルにも記載されていますが、
以下の機能の実装が必要です。
今回は、全IFの実装はしておらず、未使用機能は、未実装です。(unitsなど)
スケジュール機能で作成する必要があるテーブルは、
event,calendar,unitですが、ユーザ単位にスケジュール管理したいので、追加でuser_calendarも作成することにしました。
また、スケジュール追加時に、繰り返し機能が標準では用意されていますが、運用(実装)が少し、複雑なので、今回は、未実装(機能を停止)しています。
UI画面は、サイズに応じでコンパクト表示も可能で、スマホでも操作可能です。
汎用的なスケジュール機能が有償ライセンスを使えば使用できます。
今後、作業日報とスケジュール機能を統合して、運用できるように実用化予定です。
このスケジュール機能ですが、マウス操作でスケジュール変更が簡単(ドラッグ操作やスライド操作も可能)です。とりあえず忘れないようにスケジュールを入力して、後日、時刻情報などを修正する操作も簡単にできます。
以下のURLアクセスすると、いろいろな使い方が説明されています。