Odyssey
aBankingCalendar.prg
1 <?php
2 /**
3  * @author MGHandy: 7/28/17
4  * @package BankingCalendar
5  *
6  * @uses display dates indicating the credit union will be CLOSED for business.
7  * view/edit: the current calendar year
8  * view/edit the next calendar year
9  * view the previous calendar year
10  *
11  * @param operation string: database operation to perform
12  * operation: readCalendar, updateCalendar
13  * @param calendar string: year and list of dates to create or
14  * update.
15  *
16  * @return aryReply array: errors or information and calendar data
17  */
18 
19 require_once("$admLibrary/aBankingCalendar.i");
20 
21 try {
22  $admVars = array();
23  $admOk = array(
24  "operation" => array("filter" => FILTER_SANITIZE_STRING),
25  "calendar" => array("filter" => FILTER_SANITIZE_STRING)
26  );
27  HCU_ImportVars($admVars, "CALENDAR", $admOk);
28 
29  $cOperation = isset($admVars["CALENDAR"]["operation"]) ? $admVars["CALENDAR"]["operation"] : null;
30  $cCalendar = isset($admVars["CALENDAR"]["calendar"]) ? $admVars["CALENDAR"]["calendar"] : null;
31 
32  $aryResult = array();
33  $aryReply = array();
34 
35  switch ($cOperation) {
36  case "":
37  PrintBankingCalendar();
38  break;
39  case "readCalendar":
40  $yearCurrent = date("Y");
41  $yearNext = date("Y") + 1;
42  $yearLast = date("Y") - 1;
43 
44  $dataCurrent = BankingCalendarRead($SYSENV, $dbh, $Cu, $yearCurrent);
45  $dataLast = BankingCalendarRead($SYSENV, $dbh, $Cu, $yearLast);
46  $dataNext = BankingCalendarRead($SYSENV, $dbh, $Cu, $yearNext);
47 
48  $fechaPasado = $dataLast['calendar']['dates'];
49  $fechaAhora = $dataCurrent['calendar']['dates'];
50  $fechaSiguiente = $dataNext['calendar']['dates'];
51 
52  // New functionality: if the banking calendar is not created but there have been notifications then dates will be saved as '{"lastnotified":"11-01-2018"}'.
53  // If there are dates, then the record is saved as '["01-01-2018"]'. It is easier to check this way.
54  $aryResult['data'][$yearCurrent] = $fechaAhora && substr(trim($fechaAhora), 0, 1) != "{" ? HCU_JsonDecode($fechaAhora) : null;
55  $aryResult['data'][$yearLast] = $fechaPasado && substr(trim($fechaPasado), 0, 1) != "{" ? HCU_JsonDecode($fechaPasado) : null;
56  $aryResult['data'][$yearNext] = $fechaSiguiente && substr(trim($fechaSiguiente), 0, 1) != "{" ? HCU_JsonDecode($fechaSiguiente) : null;
57 
58  BankingCalendarReplay($aryResult, $aryReply, $cOperation);
59  break;
60  case "updateCalendar":
61  $calendarValidate = BankingCalendarValidate($SYSENV, $cCalendar, true);
62  $calendarYear = $calendarValidate['validate']['year'];
63  $calendarDates = $calendarValidate['validate']['dates'];
64 
65  $calendarExists = BankingCalendarRead($SYSENV, $dbh, $Cu, $calendarYear);
66  $calendarUpdate = null;
67  if ($calendarExists['calendar']) {
68  $calendarUpdate = BankingCalendarUpdate($SYSENV, $dbh, $Cu, $calendarYear, $calendarDates);
69  } else {
70  $calendarUpdate = BankingCalendarCreate($SYSENV, $dbh, $Cu, $calendarYear, $calendarDates);
71  }
72 
73  $aryResult['info'] = $calendarUpdate['message'];
74  $aryResult['data'] = $calendarUpdate['calendar'];
75  BankingCalendarReplay($aryResult, $aryReply, $cOperation);
76  break;
77  default:
78  throw new Exception("Unknown server request: " . $cOperation);
79  break;
80  }
81 } catch (Exception $e) {
82  $aryReply['error'][] = $e->getMessage();
83  $aryResult['data'] = array();
84  $aryResult['info'] = array();
85 
86  BankingCalendarReplay($aryResult, $aryReply, $cOperation);
87 }
88 
89 /**
90  * @uses print all html and javascript neccessary for the user to
91  * view and interact with the Banking Calendar feature.
92  */
93 function PrintBankingCalendar() { ?>
94 
95 <script type="text/javascript">
96  <?php getShowWaitFunctions(); ?>
97 
98  var yearCurrent = new Date().getFullYear();
99  var yearLast = yearCurrent -1;
100  var yearNext = yearCurrent +1;
101 
102  var calendarData = {};
103  var calendarDataSource = null;
104 
105  var window_stack = [];
106 
107  function InitDataSources() {
108 
109  calendarDataSource = new kendo.data.DataSource({
110  transport: {
111  read: {
112  url: "main.prg",
113  dataType: "json",
114  contentType: "application/x-www-form-urlencoded",
115  type: "GET",
116  data: {
117  ft: "52"
118  },
119  cache: false
120  }
121  },
122  requestStart: function(request) {
123  showWaitWindow();
124  },
125  requestEnd: function(response) {
126  setTimeout(hideWaitWindow, 500);
127 
128  if (response.hasOwnProperty("response")) {
129  if (response.response.hasOwnProperty("Results")) {
130  var results = response.response.Results;
131 
132  if (results.hasOwnProperty("error")) {
133  $.homecuValidator.homecuResetMessage = true;
134  $.homecuValidator.displayMessage(results.error, $.homecuValidator.settings.statusError);
135  } else if (results.hasOwnProperty("info")) {
136  $.homecuValidator.homecuResetMessage = true;
137  $.homecuValidator.displayMessage(results.info, $.homecuValidator.settings.statusSuccess);
138  }
139  } else {
140  $.homecuValidator.displayMessage("Error Parsing Server", $.homecuValidator.settings.statusError);
141  }
142  } else {
143  $.homecuValidator.displayMessage("Error Parsing Server", $.homecuValidator.settings.statusError);
144  }
145  },
146  schema: {
147  parse: function(data) {
148  var results = null;
149  var resultData = null;
150  var resultOperation = null;
151 
152  if (data.hasOwnProperty("Results")) {
153  results = data.Results;
154  resultData = results.data;
155  resultOperation = results.operation;
156  }
157 
158  if (results.hasOwnProperty("error")) {
159  return [];
160  }
161 
162  if (resultData === null || resultData === undefined) {
163  return [];
164  }
165 
166  switch (resultOperation) {
167  case "readCalendar":
168  calendarData[yearCurrent] = resultData[yearCurrent] ?
169  resultData[yearCurrent] : [];
170  calendarData[yearLast] = resultData[yearLast] ?
171  resultData[yearLast] : [];
172  calendarData[yearNext] = resultData[yearNext] ?
173  resultData[yearNext] : [];
174 
175  BuildCalendar(yearCurrent);
176  ResetCalendar(yearCurrent);
177  break;
178  case "updateCalendar":
179 
180  break;
181  default:
182  break;
183  }
184  return [];
185  }
186  }
187  });
188 
189  // set up initial structure for last year
190  // set up initial structure for this year
191  // set up initial structure for next year
192  calendarData[yearLast] = [];
193  calendarData[yearCurrent] = [];
194  calendarData[yearNext] = [];
195 
196  yearChanged = {
197  current: yearCurrent,
198  selected: null,
199  changed: false
200  };
201  }
202 
203  function InitDataViews() {
204 
205  // setup calendars for current year
206  // setup calendars with initial data
207  BuildCalendar(yearCurrent);
208  ResetCalendar(yearCurrent);
209 
210  // initialize dropdown for year select
211  $("#yearSelect").kendoDropDownList({
212  dataSource: [
213  { value: yearLast, text: yearLast },
214  { value: yearCurrent, text: yearCurrent },
215  { value: yearNext, text: yearNext }
216  ],
217  dataValueField: "value",
218  dataTextField: "text",
219  value: yearCurrent,
220  select: function(e) {
221  if (yearChanged.changed === false) {
222  if (HasChanges(false)) {
223  e.preventDefault();
224  yearChanged.selected = e.dataItem.value;
225  yearChanged.changed = true;
226  $("#dialogDiscard").data("kendoDialog").open();
227  }
228  }
229  },
230  change: function(e) {
231  // reset calendars to new year
232  // reset calendar dates
233  BuildCalendar(this.value());
234  ResetCalendar(this.value());
235 
236  yearChanged.changed = false;
237  yearChanged.current = parseInt(this.value());
238  }
239  }).trigger("change");
240 
241  $("#btnUpdate").on("click", function(e) {
242  if (HasChanges(true)) {
243  var _request = {
244  operation: "updateCalendar",
245  calendar: JSON.stringify({
246  year: yearChanged.current,
247  dates: calendarData[yearChanged.current]
248  })
249  };
250 
251  calendarDataSource.transport.options.read.type = "POST";
252  calendarDataSource.read(_request);
253  }
254  });
255 
256  $("#lnkCancel").on("click", function(e) {
257  if (HasChanges(false)) {
258  $("#dialogReset").data("kendoDialog").open();
259  }
260  });
261 
262  $("#dialogDiscard").kendoDialog({
263  title: "Discard Changes",
264  model: true,
265  visible: false,
266  resizable: false,
267  show: function() {
268  window_stack.push(this);
269  },
270  close: function() {
271  window_stack.pop();
272  yearChanged.changed = false;
273  },
274  actions: [
275  { text: "No",
276  action: function() { }
277  },
278  {
279  text: "Yes", primary: true,
280  action: function() {
281  $("#yearSelect").data("kendoDropDownList").value(yearChanged.selected);
282  $("#yearSelect").data("kendoDropDownList").trigger("change");
283  }
284  }
285  ]
286  });
287 
288  $("#dialogReset").kendoDialog({
289  title: "Discard Changes",
290  model: true,
291  visible: false,
292  resizable: false,
293  show: function() {
294  window_stack.push(this);
295  },
296  close: function() {
297  window_stack.pop();
298  },
299  actions: [
300  { text: "No",
301  action: function() { }
302  },
303  {
304  text: "Yes", primary: true,
305  action: function() {
306  // reset calendars for currently select year
307  // reset calendars for original data for currently select year
308  var _year = yearChanged.current;
309  BuildCalendar(_year);
310  ResetCalendar(_year);
311  }
312  }
313  ]
314  });
315  }
316 
317  function HasChanges(update) {
318  var _hasChanges = false;
319  var _year = yearChanged.current;
320  var _yearData = calendarData[_year];
321 
322  // iterate all dates
323  $("div[id^=calendar]").find("table tbody td").each(function(i) {
324  var _cell = $(this);
325  var _cellSelected = _cell.hasClass("local-date-selected");
326 
327  // check if cell isselected
328  // selected: check if not in original data
329  // not selected: check if in original data
330  var _dateString = _cell.find("a").attr("title");
331  if (_dateString) {
332  var _date = kendo.toString(new Date(_dateString), "yyyy-MM-dd");
333  var _dateIndex = $.inArray(_date, _yearData);
334 
335  if (_cellSelected) {
336  if (_dateIndex === -1) {
337  _hasChanges = true;
338 
339  // if update, add to list
340  if (update) {
341  _yearData.push(_date);
342  }
343  }
344  } else {
345  if (_dateIndex !== -1) {
346  _hasChanges = true;
347 
348  // if update, remove from list
349  if (update) {
350  _yearData.splice(_dateIndex, 1);
351  }
352  }
353  }
354  }
355 
356  });
357 
358  return _hasChanges;
359  }
360 
361  function BuildCalendar(year) {
362  var _year = parseInt(year);
363 
364  $("div[id^=calendar]").each(function(i) {
365  var _calendar = $(this);
366  // must destroy calendars if they exist
367  if (_calendar.data("kendoCalendar") !== undefined) {
368  _calendar.data("kendoCalendar").destroy();
369  _calendar.empty();
370  }
371 
372  // re create calendars
373  _calendar.kendoCalendar({
374  footer: false,
375  min: new Date(_year, i, 1),
376  max: LastDayOfMonth(_year, i),
377  change: function(e) {
378  var _cell = this._cell;
379  var _selected = _cell.hasClass("k-state-selected");
380 
381  if (_selected) {
382  _cell.removeClass("k-state-selected");
383  }
384  },
385  disableDates: function(date) {
386  // if calendar year is
387  var _weekday = date.getDay();
388  var _year = date.getFullYear();
389 
390  // if year is last year, disable everything
391  // if day is sturday or sunday disable
392  if (_year == yearLast) {
393  return true;
394  }
395 
396  if (_weekday == 0 || _weekday == 6) {
397  return true;
398  }
399  }
400  });
401 
402  // the main reason for destroying and re-creating is this event
403  // gets overridden when changing the year
404  _calendar.find("table tbody td").on("click", function() {
405  var _cell = $(this);
406  var _cellSelected = _cell.hasClass("local-date-selected");
407  var _dateString = _cell.find("a").attr("title");
408  var _year = yearChanged.current;
409  _year = parseInt(_year);
410 
411  // this click event allows clicks on
412  // cells without dates
413  if (_dateString === undefined) {
414  return false;
415  } else {
416  var _weekday = _dateString.split(",")[0];
417  if (_weekday === "Saturday" || _weekday === "Sunday") {
418  return false;
419  }
420  }
421 
422  // if calendar year iss last year, do not allow changes
423  if (_year === yearLast) {
424  return false;
425  }
426 
427  if (_cellSelected) {
428  _cell.removeClass("local-date-selected");
429  } else {
430  _cell.addClass("local-date-selected");
431  }
432  });
433  });
434  }
435 
436  function ResetCalendar(year) {
437  var _year = parseInt(year);
438  var _yearData = calendarData[_year];
439 
440  // iterate all calendars and days
441  $("div[id^=calendar]").find("table tbody td").each(function(i) {
442  var _cell = $(this);
443  var _dateString = _cell.find("a").attr("title");
444 
445  if (_dateString) {
446  var _date = kendo.toString(new Date(_dateString), "yyyy-MM-dd");
447  var _dateIndex = $.inArray(_date, _yearData);
448 
449  if (_dateIndex !== -1) {
450  _cell.addClass("local-date-selected");
451  }
452  }
453  });
454  }
455 
456  function LastDayOfMonth(year, month) {
457  var _days = 32 - new Date(year, month, 32).getDate();
458  return new Date(year, month, _days);
459  }
460 
461  $(document).ready(function() {
462  InitDataSources();
463  InitDataViews();
464 
465  // begin read
466  calendarDataSource.transport.options.read.type = "POST";
467  calendarDataSource.read({
468  operation: "readCalendar"
469  });
470  });
471 
472  $(document).on("click", ".k-overlay", function () {
473  if(window_stack.length > 0) {
474  window_stack[window_stack.length-1].close();
475  }
476  });
477 
478 </script>
479 
480 <style type="text/css">
481  #bankingCalendar {
482  max-width: 768px;
483  margin-left: 0;
484  }
485 
486  input[type="radio"],
487  input[type="checkbox"] {
488  margin-top: -3px;
489  }
490 
491  div[id^="calendar"] .k-nav-prev,
492  div[id^="calendar"] .k-nav-next {
493  display: none;
494  }
495 
496  div[id^="calendar"] .local-date-selected {
497  border-color: #1a87cd;
498  background-color: #1984c8;
499  opacity: 0.7;
500  }
501 
502  div[id^="calendar"] .local-date-selected a,
503  div[id^="calendar"] .local-date-selected.k-today a {
504  color: #fff;
505  }
506 
507 </style>
508 
509 <br>
510 <div class="container-fluid" id="bankingCalendar">
511  <div class="well well-sm col-sm-12">
512  <h2>Banking Calendar</h2>
513 
514  <br>
515 
516  <div class="row">
517  <div class="col-sm-12">
518  <div class="hcu-secondary">
519  <div class="vsgSecondary">
520  <span>Use the calendar below to mark non-business days or holidays by clicking the day. </span>
521  </div>
522  </div>
523  </div>
524  <div class="col-sm-12">
525  <input id="yearSelect">
526  </div>
527  </div>
528 
529  <br>
530 
531  <div class="row">
532  <div class="col-sm-4">
533  <div class="hcu-all-100" id="calendarJanuary"></div>
534  </div>
535 
536  <div class="col-sm-4">
537  <div class="hcu-all-100" id="calendarFebruary"></div>
538  </div>
539 
540  <div class="col-sm-4">
541  <div class="hcu-all-100" id="calendarMarch"></div>
542  </div>
543  </div>
544  <div class="row">
545  <div class="col-sm-4">
546  <div class="hcu-all-100" id="calendarApril"></div>
547  </div>
548 
549  <div class="col-sm-4">
550  <div class="hcu-all-100" id="calendarMay"></div>
551  </div>
552 
553  <div class="col-sm-4">
554  <div class="hcu-all-100" id="calendarJune"></div>
555  </div>
556  </div>
557  <div class="row">
558  <div class="col-sm-4">
559  <div class="hcu-all-100" id="calendarJuly"></div>
560  </div>
561 
562  <div class="col-sm-4">
563  <div class="hcu-all-100" id="calendarAugust"></div>
564  </div>
565 
566  <div class="col-sm-4">
567  <div class="hcu-all-100" id="calendarSeptember"></div>
568  </div>
569  </div>
570  <div class="row">
571  <div class="col-sm-4">
572  <div class="hcu-all-100" id="calendarOctober"></div>
573  </div>
574 
575  <div class="col-sm-4">
576  <div class="hcu-all-100" id="calendarNovember"></div>
577  </div>
578 
579  <div class="col-sm-4">
580  <div class="hcu-all-100" id="calendarDecember"></div>
581  </div>
582  </div>
583  </div>
584 
585  <div class="hcu-template">
586  <div class="hcu-edit-buttons k-state-default">
587  <a href="##" id="lnkCancel" style=""
588  data-bind="events:{ click: scheduleCancel }">Cancel</a>
589  &emsp;
590  <a href="##" id="btnUpdate" class="k-button k-primary"
591  data-bind="
592  events:{ click: scheduleSave }">
593  <i class="fa fa-check fa-lg"></i>Update
594  </a>
595  </div>
596  </div>
597 </div>
598 
599 <div id="dialogDiscard">
600  <p>You are about to change years, any changes to this calendar will be lost.</p>
601  <p>Do you wish to continue?</p>
602 </div>
603 
604 <div id="dialogReset">
605  <p>You are about to reset this calendar to the original data.</p>
606  <p>Do you wish to continue?</p>
607 </div>
608 
609 <?php }