Calendar.xaml.cs 57 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.ObjectModel;
  4. using System.ComponentModel;
  5. using System.Diagnostics.CodeAnalysis;
  6. using System.Linq;
  7. using System.Windows;
  8. using System.Windows.Controls;
  9. using System.Windows.Controls.Primitives;
  10. using System.Windows.Data;
  11. using System.Windows.Media;
  12. using System.Windows.Threading;
  13. using Comal.Classes;
  14. using InABox.Clients;
  15. using InABox.Configuration;
  16. using InABox.Core;
  17. using InABox.DynamicGrid;
  18. using InABox.Wpf;
  19. using InABox.WPF;
  20. using PRSDesktop.Grids;
  21. using Selection = InABox.Core.Selection;
  22. using SelectionChangedEventArgs = System.Windows.Controls.SelectionChangedEventArgs;
  23. namespace PRSDesktop
  24. {
  25. public partial class Calendar
  26. {
  27. private enum Suppress
  28. {
  29. Selector, // Prevent the Selector from Being Changed
  30. Calendar, // Prevent the Calendar from Being Reconfigured
  31. Events, // Prevent the Selectors from Responding to Events
  32. Refresh, // Stop the Data from Being refreshed
  33. Settings // Dont allow settings to be updated
  34. }
  35. private EventSuppressor? suppressor = null;
  36. public void DisableUpdate()
  37. {
  38. suppressor ??= new EventSuppressor(Suppress.Refresh, Suppress.Settings);
  39. }
  40. public void EnableUpdate()
  41. {
  42. if (suppressor != null)
  43. {
  44. suppressor.Dispose();
  45. suppressor = null;
  46. }
  47. }
  48. #region Converters
  49. public static readonly IValueConverter DayOfWeekConverter = new FuncConverter<DateTime, string>(x => x.DayOfWeek.ToString());
  50. #endregion
  51. #region Dependency Properties
  52. #region HeaderVisibility Dependency Property
  53. public static readonly DependencyProperty HeaderVisibilityProperty =
  54. DependencyProperty.Register(
  55. nameof(HeaderVisibility),
  56. typeof(Visibility),
  57. typeof(Calendar),
  58. new UIPropertyMetadata(Visibility.Collapsed)
  59. );
  60. public Visibility HeaderVisibility
  61. {
  62. get => (Visibility)GetValue(HeaderVisibilityProperty);
  63. set => SetValue(HeaderVisibilityProperty,value);
  64. }
  65. #endregion
  66. #region SettingsVisible Dependency Property
  67. public static readonly DependencyProperty SettingsVisibleProperty =
  68. DependencyProperty.Register(
  69. nameof(SettingsVisible),
  70. typeof(CalendarSettingsVisibility),
  71. typeof(Calendar),
  72. new UIPropertyMetadata(CalendarSettingsVisibility.Disabled, SettingsVisible_Changed)
  73. );
  74. private static void SettingsVisible_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
  75. {
  76. if (d is not Calendar calendar) return;
  77. var value = (CalendarSettingsVisibility)e.NewValue;
  78. if (!EventSuppressor.IsSet(Suppress.Settings))
  79. {
  80. calendar.Properties.SettingsVisible = value;
  81. calendar.DoSaveSettings();
  82. }
  83. calendar._splitPanel.View = value == CalendarSettingsVisibility.Visible
  84. ? DynamicSplitPanelView.Combined
  85. : DynamicSplitPanelView.Master;
  86. calendar._splitPanel.AllowableViews = value == CalendarSettingsVisibility.Disabled
  87. ? DynamicSplitPanelView.Master
  88. : DynamicSplitPanelView.Master | DynamicSplitPanelView.Combined;
  89. }
  90. public CalendarSettingsVisibility SettingsVisible
  91. {
  92. get => (CalendarSettingsVisibility)GetValue(SettingsVisibleProperty);
  93. set => SetValue(SettingsVisibleProperty, value);
  94. }
  95. #endregion
  96. #region CalendarView Dependency Property
  97. public static readonly DependencyProperty CalendarViewProperty =
  98. DependencyProperty.Register(
  99. nameof(CalendarView),
  100. typeof(CalendarViewType),
  101. typeof(Calendar),
  102. new UIPropertyMetadata(CalendarViewType.Day, CalendarView_Changed)
  103. );
  104. public CalendarViewType CalendarView
  105. {
  106. get => (CalendarViewType)GetValue(CalendarViewProperty);
  107. set => SetValue(CalendarViewProperty, value);
  108. }
  109. private static void CalendarView_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
  110. {
  111. if (d is not Calendar calendar) return;
  112. if (!EventSuppressor.IsSet(Suppress.Settings))
  113. {
  114. calendar.Properties.CalendarView = calendar.CalendarView;
  115. calendar.DoSaveSettings();
  116. }
  117. calendar.Refresh();
  118. }
  119. #endregion
  120. #region EmployeeSelector Dependency Property
  121. public static readonly DependencyProperty EmployeeSelectionProperty =
  122. DependencyProperty.Register(
  123. nameof(EmployeeSelection),
  124. typeof(EmployeeSelectorData),
  125. typeof(Calendar),
  126. new UIPropertyMetadata(new EmployeeSelectorData(), EmployeeSelection_Changed)
  127. );
  128. public EmployeeSelectorData EmployeeSelection
  129. {
  130. get => (EmployeeSelectorData)GetValue(EmployeeSelectionProperty);
  131. set => SetValue(EmployeeSelectionProperty, value);
  132. }
  133. private static void EmployeeSelection_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
  134. {
  135. if (d is not Calendar calendar) return;
  136. if (!EventSuppressor.IsSet(Suppress.Settings))
  137. {
  138. calendar.Properties.EmployeeSelection = calendar.EmployeeSelection;
  139. calendar.DoSaveSettings();
  140. }
  141. calendar.EmployeeSelector.Selection = calendar.EmployeeSelection;
  142. calendar._employees = calendar.EmployeeSelector.GetEmployeeData((row, rosters) => row.ToObject<Employee>());
  143. calendar.ReloadColumns();
  144. calendar.Refresh();
  145. }
  146. private void EmployeeSelector_OnSelectionChanged(object sender, EmployeeSelectorSelectionChangedArgs args)
  147. {
  148. if (EventSuppressor.IsSet(Suppress.Events))
  149. return;
  150. EmployeeSelection = args.Selection;
  151. }
  152. #endregion
  153. #region EmployeeSettings Dependency Property
  154. public static readonly DependencyProperty EmployeeSettingsProperty =
  155. DependencyProperty.Register(
  156. nameof(EmployeeSettings),
  157. typeof(EmployeeSelectorSettings),
  158. typeof(Calendar),
  159. new UIPropertyMetadata(new EmployeeSelectorSettings(), EmployeeSettings_Changed)
  160. );
  161. private static void EmployeeSettings_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
  162. {
  163. if (d is not Calendar calendar) return;
  164. if (!EventSuppressor.IsSet(Suppress.Settings))
  165. {
  166. calendar.Properties.EmployeeSelector = calendar.EmployeeSettings;
  167. calendar.DoSaveSettings();
  168. }
  169. calendar.EmployeeSelector.Settings = calendar.EmployeeSettings;
  170. }
  171. public EmployeeSelectorSettings EmployeeSettings
  172. {
  173. get => (EmployeeSelectorSettings)GetValue(EmployeeSettingsProperty);
  174. set => SetValue(EmployeeSettingsProperty, value);
  175. }
  176. private void EmployeeSelector_OnSettingsChanged(object sender, EmployeeSelectorSettingsChangedArgs args)
  177. {
  178. if (EventSuppressor.IsSet(Suppress.Events))
  179. return;
  180. EmployeeSettings = args.Settings;
  181. }
  182. #endregion
  183. #region TimeInterval DependencyProperty
  184. public static readonly DependencyProperty TimeIntervalProperty =
  185. DependencyProperty.Register(
  186. "TimeInterval",
  187. typeof(CalendarTimeInterval),
  188. typeof(Calendar),
  189. new PropertyMetadata(CalendarTimeInterval.FifteenMinutes, TimeInterval_Changed)
  190. );
  191. public CalendarTimeInterval TimeInterval
  192. {
  193. get => (CalendarTimeInterval)GetValue(TimeIntervalProperty);
  194. set => SetValue(TimeIntervalProperty, value);
  195. }
  196. private static void TimeInterval_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
  197. {
  198. if (d is not Calendar calendar) return;
  199. if (!EventSuppressor.IsSet(Suppress.Settings))
  200. {
  201. calendar.Properties.TimeInterval = calendar.TimeInterval;
  202. calendar.DoSaveSettings();
  203. }
  204. calendar.IntervalSelector.SelectedIndex = (int)calendar.TimeInterval;
  205. calendar.CalendarControl.RowInterval = TimeIntervalToTimeSpan(calendar.TimeInterval);
  206. }
  207. private void IntervalSelector_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
  208. {
  209. if (EventSuppressor.IsSet(Suppress.Events))
  210. return;
  211. TimeInterval = (CalendarTimeInterval)IntervalSelector.SelectedIndex;
  212. }
  213. private static TimeSpan TimeIntervalToTimeSpan(CalendarTimeInterval interval)
  214. {
  215. return interval switch
  216. {
  217. CalendarTimeInterval.FiveMinutes => new TimeSpan(0, 5, 0),
  218. CalendarTimeInterval.SixMinutes => new TimeSpan(0, 6, 0),
  219. CalendarTimeInterval.TenMinutes => new TimeSpan(0, 10, 0),
  220. CalendarTimeInterval.FifteenMinutes => new TimeSpan(0, 15, 0),
  221. CalendarTimeInterval.TwentyMinutes => new TimeSpan(0, 20, 0),
  222. CalendarTimeInterval.ThirtyMinutes => new TimeSpan(0, 30, 0),
  223. CalendarTimeInterval.SixtyMinutes or _ => new TimeSpan(1, 0, 0)
  224. };
  225. }
  226. #endregion
  227. #region SelectedDate Dependency Property
  228. public static readonly DependencyProperty SelectedDateProperty =
  229. DependencyProperty.Register(
  230. nameof(SelectedDate),
  231. typeof(DateTime),
  232. typeof(Calendar),
  233. new UIPropertyMetadata(DateTime.Today, SelectedDate_Changed)
  234. );
  235. public DateTime SelectedDate
  236. {
  237. get => (DateTime)GetValue(SelectedDateProperty);
  238. set => SetValue(SelectedDateProperty, value);
  239. }
  240. private static void SelectedDate_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
  241. {
  242. if (d is not Calendar calendar) return;
  243. if (!EventSuppressor.IsSet(Suppress.Settings))
  244. {
  245. calendar.Properties.Date = calendar.SelectedDate;
  246. calendar.DoSaveSettings();
  247. }
  248. calendar.Refresh();
  249. }
  250. public DateTime StartDate => (Properties?.CalendarView ?? CalendarViewType.Day) == CalendarViewType.Day
  251. ? SelectedDate
  252. : SelectedDate.StartOfWeek(DayOfWeek.Monday);
  253. public DateTime EndDate => Properties.CalendarView == CalendarViewType.Day
  254. ? StartDate.AddDays(1)
  255. : Properties.CalendarView == CalendarViewType.WorkWeek
  256. ? StartDate.AddDays(5)
  257. : StartDate.AddDays(7);
  258. #endregion
  259. #region StartHour Dependency Properties
  260. public static readonly DependencyProperty StartHourProperty =
  261. DependencyProperty.Register(
  262. nameof(StartHour),
  263. typeof(int),
  264. typeof(Calendar),
  265. new UIPropertyMetadata(6, StartHour_Changed, StartHour_Coerce)
  266. );
  267. private static object StartHour_Coerce(DependencyObject d, object baseValue)
  268. {
  269. if (d is not Calendar calendar || baseValue is not int value) return baseValue;
  270. return Math.Min(calendar.EndHour - 1, Math.Max(0, value));
  271. }
  272. public int StartHour
  273. {
  274. get => (int)GetValue(StartHourProperty);
  275. set => SetValue(StartHourProperty, value);
  276. }
  277. private static void StartHour_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
  278. {
  279. if (d is not Calendar calendar) return;
  280. if (!EventSuppressor.IsSet(Suppress.Settings))
  281. {
  282. calendar.Properties.StartHour = calendar.StartHour;
  283. calendar.DoSaveSettings();
  284. }
  285. calendar.StartTimeSelector.Text = FormatHour(calendar.StartHour);
  286. calendar.CalendarControl.StartHour = TimeSpan.FromHours(calendar.StartHour);
  287. }
  288. private void StartTimeSelector_Down_Click(object sender, RoutedEventArgs e)
  289. {
  290. if (EventSuppressor.IsSet(Suppress.Events))
  291. return;
  292. StartHour -= 1;
  293. }
  294. private void StartTimeSelector_Up_Click(object sender, RoutedEventArgs e)
  295. {
  296. if (EventSuppressor.IsSet(Suppress.Events))
  297. return;
  298. StartHour += 1;
  299. }
  300. #endregion
  301. #region End Hour Property
  302. public static readonly DependencyProperty EndHourProperty =
  303. DependencyProperty.Register(
  304. nameof(EndHour),
  305. typeof(int),
  306. typeof(Calendar),
  307. new UIPropertyMetadata(18, EndHour_Changed, EndHour_Coerce)
  308. );
  309. public int EndHour
  310. {
  311. get => (int)GetValue(EndHourProperty);
  312. set => SetValue(EndHourProperty, value);
  313. }
  314. private static object EndHour_Coerce(DependencyObject d, object baseValue)
  315. {
  316. if (d is not Calendar calendar || baseValue is not int value) return baseValue;
  317. return Math.Max(calendar.StartHour + 1, Math.Min(24, value));
  318. }
  319. private static void EndHour_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
  320. {
  321. if (d is not Calendar calendar) return;
  322. if (!EventSuppressor.IsSet(Suppress.Settings))
  323. {
  324. calendar.Properties.EndHour = calendar.EndHour;
  325. calendar.DoSaveSettings();
  326. }
  327. calendar.FinishTimeSelector.Text = FormatHour(calendar.EndHour);
  328. calendar.CalendarControl.EndHour = TimeSpan.FromHours(calendar.EndHour);
  329. }
  330. private void FinishTimeSelector_Down_Click(object sender, RoutedEventArgs e)
  331. {
  332. if (EventSuppressor.IsSet(Suppress.Events))
  333. return;
  334. EndHour -= 1;
  335. }
  336. private void FinishTimeSelector_Up_Click(object sender, RoutedEventArgs e)
  337. {
  338. if (EventSuppressor.IsSet(Suppress.Events))
  339. return;
  340. EndHour += 1;
  341. }
  342. private static string FormatHour(int hour)
  343. {
  344. return hour <= 0 || hour >= 24
  345. ? "Midnight"
  346. : hour < 12
  347. ? string.Format("{0}:00 AM", hour)
  348. : hour > 12
  349. ? string.Format("{0}:00 PM", hour)
  350. : "12:00 NN";
  351. }
  352. #endregion
  353. #region AssignmentType Dependency Property
  354. public static readonly DependencyProperty AssignmentTypeProperty =
  355. DependencyProperty.Register(
  356. nameof(AssignmentType),
  357. typeof(CalendarAssignmentType),
  358. typeof(Calendar),
  359. new UIPropertyMetadata(CalendarAssignmentType.Booked, AssignmentType_Changed)
  360. );
  361. public CalendarAssignmentType AssignmentType
  362. {
  363. get => (CalendarAssignmentType)GetValue(AssignmentTypeProperty);
  364. set => SetValue(AssignmentTypeProperty, value);
  365. }
  366. private static void AssignmentType_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
  367. {
  368. if (d is not Calendar calendar) return;
  369. if (!EventSuppressor.IsSet(Suppress.Settings))
  370. {
  371. calendar.Properties.AssignmentType = calendar.AssignmentType;
  372. calendar.DoSaveSettings();
  373. }
  374. calendar.AssignmentTypeSelector.SelectedIndex = (int)calendar.AssignmentType;
  375. calendar.Refresh();
  376. }
  377. private void AssignmentTypeSelector_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
  378. {
  379. if (EventSuppressor.IsSet(Suppress.Events))
  380. return;
  381. using (new EventSuppressor(Suppress.Selector))
  382. {
  383. AssignmentType = (CalendarAssignmentType)AssignmentTypeSelector.SelectedIndex;
  384. }
  385. }
  386. #endregion
  387. #region BackgroundType Dependency Property
  388. public static readonly DependencyProperty BackgroundTypeProperty =
  389. DependencyProperty.Register(
  390. nameof(BackgroundType),
  391. typeof(CalendarBackgroundType),
  392. typeof(Calendar),
  393. new UIPropertyMetadata(CalendarBackgroundType.Roster, BackgroundType_Changed)
  394. );
  395. public CalendarBackgroundType BackgroundType
  396. {
  397. get => (CalendarBackgroundType)GetValue(BackgroundTypeProperty);
  398. set => SetValue(BackgroundTypeProperty, value);
  399. }
  400. private static void BackgroundType_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
  401. {
  402. if (d is not Calendar calendar) return;
  403. if (!EventSuppressor.IsSet(Suppress.Settings))
  404. {
  405. calendar.Properties.BackgroundType = calendar.BackgroundType;
  406. calendar.DoSaveSettings();
  407. }
  408. calendar.BackgroundTypeSelector.SelectedIndex = (int)calendar.BackgroundType;
  409. calendar.Refresh();
  410. }
  411. private void BackgroundTypeSelector_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
  412. {
  413. if (EventSuppressor.IsSet(Suppress.Events))
  414. return;
  415. using (new EventSuppressor(Suppress.Selector))
  416. BackgroundType = (CalendarBackgroundType)BackgroundTypeSelector.SelectedIndex;
  417. }
  418. #endregion
  419. #region Zoom Dependency Properties
  420. public static readonly DependencyProperty ZoomProperty =
  421. DependencyProperty.Register(
  422. nameof(Zoom),
  423. typeof(double),
  424. typeof(Calendar),
  425. new UIPropertyMetadata(100.0, Zoom_Changed, Zoom_Coerce)
  426. );
  427. private static object Zoom_Coerce(DependencyObject d, object baseValue)
  428. {
  429. if (baseValue is not double zoom) return baseValue;
  430. return Math.Max(zoom, 100.0);
  431. }
  432. public double Zoom
  433. {
  434. get => (double)GetValue(ZoomProperty);
  435. set => SetValue(ZoomProperty, value);
  436. }
  437. private static void Zoom_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
  438. {
  439. if (d is not Calendar calendar) return;
  440. calendar.CalendarControl.Zoom = calendar.Zoom / 100;
  441. calendar.ZoomSelector.Text = $"{calendar.Zoom:F0}%";
  442. }
  443. private void ZoomSelector_Down_Click(object sender, RoutedEventArgs e)
  444. {
  445. if (EventSuppressor.IsSet(Suppress.Events))
  446. return;
  447. ZoomOut();
  448. }
  449. private void ZoomSelector_Up_Click(object sender, RoutedEventArgs e)
  450. {
  451. if (EventSuppressor.IsSet(Suppress.Events))
  452. return;
  453. ZoomIn();
  454. }
  455. public void ZoomIn() => Zoom *= 1.125;
  456. public void ZoomOut() => Zoom /= 1.125;
  457. public void ResetZoom() => Zoom = 100;
  458. #endregion
  459. #endregion
  460. #region Event Handlers
  461. public event LoadSettings<CalendarSettings>? LoadSettings;
  462. public event SaveSettings<CalendarSettings>? SaveSettings;
  463. public CalendarConfigurationEvent? ConfigurationChanged;
  464. public event CalendarDataMenuEvent? CustomiseContextMenu;
  465. public event CalendarDataEvent? SelectionChanged;
  466. public event CalendarDataEvent? ItemCreated;
  467. public event CalendarDataEvent? ItemChanged;
  468. public event CalendarHandledEvent? ItemEditing;
  469. #endregion
  470. public void SelectEmployee(Guid employeeid) => EmployeeSelector.SelectEmployee(employeeid);
  471. // Populated as requiew when EmployeeSelector.SelectionChanged is triggered
  472. private Employee[] _employees = [];
  473. // Populated once at startup
  474. private StandardLeave[] _standardleaves = [];
  475. private LeaveRequest[] _leaverequests = [];
  476. // Populated on each Refresh
  477. private TimeSheet[] _timesheets = [];
  478. // Populated on each Refresh
  479. private List<Assignment> _assignments = [];
  480. private ObservableCollection<ICalendarAppointment> _appointments = new();
  481. private bool bColumnsLoaded;
  482. private AssignmentGrid? ag;
  483. private LeaveRequestGrid? lg;
  484. private StandardLeaveGrid? slg;
  485. private DynamicDataGrid<Meeting>? mg;
  486. public bool IsReady { get; set; }
  487. public CalendarSettings Properties { get; set; } = new();
  488. public Calendar()
  489. {
  490. using (EventSuppressor.All<Suppress>())
  491. {
  492. InitializeComponent();
  493. _splitPanel.DataContext = this;
  494. StartHour = 0;
  495. EndHour = 24;
  496. }
  497. CalendarControl.GetFillBlockStart = GetFillBlockStart;
  498. CalendarControl.GetFillBlockEnd = GetFillBlockEnd;
  499. CalendarControl.RowInterval = TimeIntervalToTimeSpan(TimeInterval);
  500. CalendarViewSelector.ItemsSource = new List<KeyValuePair<CalendarViewType, string>>
  501. {
  502. new(CalendarViewType.Day, "Day"),
  503. new(CalendarViewType.WorkWeek, "Work Week"),
  504. new(CalendarViewType.Week, "Week"),
  505. };
  506. CalendarViewSelector.DisplayMemberPath = "Value";
  507. CalendarViewSelector.SelectedValuePath = "Key";
  508. }
  509. public virtual void Setup()
  510. {
  511. using (new EventSuppressor(Suppress.Settings, Suppress.Refresh, Suppress.Events))
  512. {
  513. Properties = LoadSettings?.Invoke(this) ?? new CalendarSettings();
  514. SettingsVisible = Properties.SettingsVisible;
  515. SelectedDate = Properties.AlwaysStartOnToday ? DateTime.Today : Properties.Date;
  516. StartHour = Properties.StartHour;
  517. EndHour = Properties.EndHour;
  518. TimeInterval = Properties.TimeInterval;
  519. AssignmentType = Properties.AssignmentType;
  520. BackgroundType = Properties.BackgroundType;
  521. CalendarView = Properties.CalendarView;
  522. Zoom = Properties.Zoom;
  523. EmployeeSelector.Setup();
  524. EmployeeSettings = Properties.EmployeeSelector;
  525. EmployeeSelection = Properties.EmployeeSelection;
  526. AlwaysTodayBox.IsChecked = Properties.AlwaysStartOnToday;
  527. ReloadColumns();
  528. }
  529. }
  530. public virtual void Shutdown(CancelEventArgs? cancel)
  531. {
  532. }
  533. private void DoSaveSettings()
  534. {
  535. SaveSettings?.Invoke(this, Properties);
  536. }
  537. private bool bFirst = true;
  538. public virtual void Refresh()
  539. {
  540. RefreshData(StartDate, EndDate);
  541. }
  542. private bool _refreshing = false;
  543. private void RefreshData(DateTime startDate, DateTime endDate)
  544. {
  545. if (EventSuppressor.IsSet(Suppress.Refresh))
  546. return;
  547. if (!_refreshing)
  548. {
  549. _refreshing = true;
  550. Dispatcher.BeginInvoke(() => DoRefreshData(startDate, endDate));
  551. }
  552. }
  553. private void DoRefreshData(DateTime startDate, DateTime endDate)
  554. {
  555. _refreshing = false;
  556. using (new WaitCursor())
  557. {
  558. if (!bColumnsLoaded)
  559. ReloadColumns();
  560. var query = new MultiQuery();
  561. var empids = _employees.ToArray(x => x.ID);
  562. if (BackgroundType != CalendarBackgroundType.Roster)
  563. {
  564. query.Add(
  565. Filter<TimeSheet>.Where(x => x.EmployeeLink.ID).InList(empids)
  566. .And(x => x.Date).IsGreaterThanOrEqualTo(startDate)
  567. .And(x => x.Date).IsLessThanOrEqualTo(endDate)
  568. .And(x=>x.LeaveRequestLink.ID).IsEqualTo(Guid.Empty)
  569. .And(x=>x.StandardLeaveLink.ID).IsEqualTo(Guid.Empty),
  570. TimeSheetModel.Columns
  571. );
  572. }
  573. query.Add(
  574. Filter<Assignment>.Where(x => x.EmployeeLink.ID).InList(empids)
  575. .And(x => x.Date).IsGreaterThanOrEqualTo(startDate)
  576. .And(x => x.Date).IsLessThanOrEqualTo(endDate),
  577. AssignmentModel.Columns,
  578. new SortOrder<Assignment>(x => x.EmployeeLink.ID).ThenBy(x => x.Date).ThenBy(x => x.Booked.Duration, SortDirection.Descending)
  579. );
  580. query.Add(
  581. Filter<LeaveRequest>
  582. .Where(x => x.Status).IsNotEqualTo(LeaveRequestStatus.Rejected)
  583. .And(x => x.EmployeeLink.ID).InList(empids)
  584. .And(x => x.From).IsLessThanOrEqualTo(endDate)
  585. .And(x => x.To).IsGreaterThanOrEqualTo(startDate),
  586. Columns.None<LeaveRequest>()
  587. .Add(x => x.ID)
  588. .Add(x => x.EmployeeLink.ID)
  589. .Add(x => x.From)
  590. .Add(x => x.FromTime)
  591. .Add(x => x.To)
  592. .Add(x => x.ToTime)
  593. .Add(x => x.LeaveType.Description)
  594. .Add(x => x.LeaveType.Color)
  595. .Add(x => x.Status)
  596. .Add(x => x.Notes));
  597. query.Add(
  598. Filter<StandardLeave>
  599. .Where(x => x.From).IsLessThanOrEqualTo(endDate)
  600. .And(x => x.To).IsGreaterThanOrEqualTo(startDate),
  601. Columns.None<StandardLeave>()
  602. .Add(x => x.ID)
  603. .Add(c => c.ID)
  604. .Add(c => c.LeaveType.Description)
  605. .Add(c => c.Name)
  606. .Add(c => c.LeaveType.Color)
  607. .Add(c => c.From)
  608. .Add(c => c.FromTime)
  609. .Add(c => c.To)
  610. .Add(c => c.ToTime));
  611. query.Query();
  612. _timesheets = (BackgroundType == CalendarBackgroundType.Roster)
  613. ? []
  614. : query.Get<TimeSheet>().ToArray<TimeSheet>();
  615. _leaverequests = query.Get<LeaveRequest>().ToArray<LeaveRequest>();
  616. _assignments = query.Get<Assignment>().ToList<Assignment>();
  617. _standardleaves = query.Get<StandardLeave>().ToArray<StandardLeave>();
  618. LoadBackground();
  619. _appointments.Clear();
  620. LoadStandardLeaves(_appointments);
  621. LoadLeaveRequests(_appointments);
  622. LoadAssignments(_appointments);
  623. try
  624. {
  625. CalendarControl.Dates = CoreUtils.Range(startDate, endDate, x => x.AddDays(1));
  626. CalendarControl.ItemsSource = _appointments;
  627. }
  628. catch (Exception e)
  629. {
  630. Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
  631. }
  632. bFirst = false;
  633. }
  634. }
  635. public EmployeeRosterItem? GetRoster(Guid employeeid, DateTime date)
  636. {
  637. var employee = _employees.FirstOrDefault(x => x.ID == employeeid);
  638. if (employee is null) return null;
  639. var roster = RosterUtils.GetRoster(EmployeeSelector.GetRoster(employeeid), employee.RosterStart, date);
  640. return roster;
  641. }
  642. public bool GetActiveWindow(Guid employeeid, DateTime date, ref TimeSpan start, ref TimeSpan finish)
  643. {
  644. var result = false;
  645. foreach (var assignment in _assignments.Where(a => (a.EmployeeLink.ID == employeeid) && (a.Date) == date))
  646. {
  647. result = true;
  648. var curstart = AssignmentType switch
  649. {
  650. CalendarAssignmentType.Booked => assignment.Booked.Start,
  651. CalendarAssignmentType.Actual => assignment.Actual.Start,
  652. _ => Assignment.EffectiveTime(assignment.Actual.Start, assignment.Booked.Start)
  653. };
  654. var curfinish = AssignmentType switch
  655. {
  656. CalendarAssignmentType.Booked => assignment.Booked.Finish,
  657. CalendarAssignmentType.Actual => assignment.Actual.Finish,
  658. _ => Assignment.EffectiveTime(
  659. assignment.Actual.Finish,
  660. Assignment.EffectiveTime(assignment.Actual.Start, assignment.Booked.Start)
  661. .Add(assignment.Booked.Duration)
  662. )
  663. };
  664. start = start > curstart ? curstart : start;
  665. finish = finish < curfinish ? curfinish : finish;
  666. }
  667. if ((BackgroundType == CalendarBackgroundType.Roster) ||
  668. ((BackgroundType == CalendarBackgroundType.Automatic) && (date >= DateTime.Today)))
  669. {
  670. var employee = _employees.FirstOrDefault(x => x.ID == employeeid);
  671. if (employee != null)
  672. {
  673. var roster = RosterUtils.GetRoster(EmployeeSelector.GetRoster(employeeid), employee.RosterStart, date);
  674. if (roster != null)
  675. {
  676. var blocks = roster.GetBlocks(date, TimeSpan.MinValue, TimeSpan.MaxValue);
  677. foreach (var block in blocks)
  678. {
  679. start = start > block.Start ? block.Start : start;
  680. finish = finish < block.Finish ? block.Finish : finish;
  681. }
  682. }
  683. }
  684. }
  685. else
  686. {
  687. foreach (var timesheet in _timesheets.Where(t => (t.EmployeeLink.ID == employeeid) && (t.Date == date)))
  688. {
  689. result = true;
  690. var curstart = !timesheet.Approved.IsEmpty()
  691. ? timesheet.ApprovedStart
  692. : timesheet.Start;
  693. var curfinish = !timesheet.Approved.IsEmpty()
  694. ? timesheet.ApprovedFinish
  695. : timesheet.Finish;
  696. start = start > curstart ? curstart : start;
  697. finish = finish < curfinish ? curfinish : finish;
  698. }
  699. }
  700. return result;
  701. }
  702. public Assignment[] GetAssignments(Guid employeeid, DateTime date)
  703. {
  704. return _assignments.Where(a => (a.EmployeeLink.ID == employeeid) && (a.Date == date)).ToArray();
  705. }
  706. private void LoadBackground()
  707. {
  708. var regions = new ObservableCollection<CalendarRegion>();
  709. foreach (var employee in _employees)
  710. {
  711. for (var date = StartDate; date < EndDate; date = date.AddDays(1))
  712. {
  713. if ((BackgroundType == CalendarBackgroundType.Roster) ||
  714. ((BackgroundType == CalendarBackgroundType.Automatic) && (date >= DateTime.Today)))
  715. {
  716. var roster = RosterUtils.GetRoster(EmployeeSelector.GetRoster(employee.ID), employee.RosterStart, date);
  717. if (roster != null)
  718. {
  719. var blocks = roster.GetBlocks(date, TimeSpan.FromSeconds(0), TimeSpan.FromDays(1));
  720. foreach (var block in blocks)
  721. {
  722. regions.Add(new CalendarRegion
  723. {
  724. Date = date,
  725. Column = employee,
  726. Start = block.Start,
  727. End = block.Finish,
  728. Background = Colors.Yellow.ToBrush(0.3)
  729. });
  730. }
  731. }
  732. }
  733. else
  734. {
  735. foreach (var timesheet in _timesheets.Where(t => (t.EmployeeLink.ID == employee.ID) && (t.Date == date)))
  736. {
  737. var start = !timesheet.Approved.IsEmpty()
  738. ? timesheet.ApprovedStart
  739. : timesheet.Start;
  740. var finish = !timesheet.Approved.IsEmpty()
  741. ? timesheet.ApprovedFinish
  742. : timesheet.Finish;
  743. if(finish == default)
  744. {
  745. finish = TimeSpan.FromHours(24);
  746. }
  747. regions.Add(new CalendarRegion
  748. {
  749. Date = date,
  750. Column = employee,
  751. Start = start,
  752. End = finish,
  753. Background = (!timesheet.Approved.IsEmpty() ? Colors.LightGreen : Colors.LightSalmon).ToBrush(0.4),
  754. });
  755. }
  756. }
  757. }
  758. }
  759. CalendarControl.Regions = regions;
  760. }
  761. private void LoadStandardLeaves(IList<ICalendarAppointment> appointments)
  762. {
  763. for (var date = StartDate; date < EndDate; date = date.AddDays(1))
  764. {
  765. var leaves = _standardleaves.Where(x =>
  766. (x.From <= date)
  767. && (x.To.Add(x.ToTime) > date)
  768. ).ToArray();
  769. foreach (var leave in leaves)
  770. {
  771. foreach (var employee in _employees)
  772. {
  773. var start = (date.Date == leave.From.Date) ? leave.FromTime : TimeSpan.FromSeconds(0);
  774. var finish = (date.Date == leave.To.Date) ? leave.ToTime : TimeSpan.FromDays(1).Subtract(TimeSpan.FromSeconds(1));
  775. var roster = RosterUtils.GetRoster(EmployeeSelector.GetRoster(employee.ID), employee.RosterStart, date);
  776. if (roster != null)
  777. {
  778. var blocks = roster.GetBlocks(date, start, finish);
  779. foreach (var block in blocks)
  780. {
  781. var appt = new StandardLeaveAppointment(leave, employee, block);
  782. appointments.Add(appt);
  783. }
  784. }
  785. }
  786. }
  787. }
  788. }
  789. private void LoadLeaveRequests(IList<ICalendarAppointment> appointments)
  790. {
  791. for (var date = StartDate; date < EndDate; date = date.AddDays(1))
  792. {
  793. var ids = _employees.ToArray(x => x.ID);
  794. var leaves = _leaverequests.Where(x =>
  795. (x.From <= date)
  796. && (x.To.Add(x.ToTime) > date)
  797. && ids.Contains(x.EmployeeLink.ID)
  798. ).ToArray();
  799. foreach (var leave in leaves)
  800. {
  801. var employee = _employees.FirstOrDefault(x => x.ID == leave.EmployeeLink.ID);
  802. if (employee is null) return;
  803. var roster = RosterUtils.GetRoster(EmployeeSelector.GetRoster(employee.ID), employee.RosterStart, date);
  804. if (roster != null)
  805. {
  806. var start = (date.Date == leave.From.Date) ? leave.FromTime : TimeSpan.FromSeconds(0);
  807. var finish = (date.Date == leave.To.Date) ? leave.ToTime : TimeSpan.FromDays(1).Subtract(TimeSpan.FromSeconds(1));
  808. var blocks = roster.GetBlocks(date, start, finish);
  809. foreach(var block in blocks)
  810. {
  811. appointments.Add(new LeaveRequestAppointment(leave, employee, block));
  812. }
  813. }
  814. }
  815. }
  816. }
  817. private HashSet<Assignment> _savingAssignments = new();
  818. private void LoadAssignment(Assignment assignment, IList<ICalendarAppointment> appointments)
  819. {
  820. var employee = _employees.FirstOrDefault(x => x.ID == assignment.EmployeeLink.ID);
  821. if (employee is null) return;
  822. var model = new AssignmentAppointment(assignment, employee, AssignmentType);
  823. model.OnUpdate += () =>
  824. {
  825. if (_savingAssignments.Add(assignment))
  826. {
  827. Dispatcher.BeginInvoke(() =>
  828. {
  829. _savingAssignments.Remove(assignment);
  830. Client.SaveAsync(assignment, "Edited by user").LogIfFail();
  831. });
  832. }
  833. };
  834. model.EmployeeChanged += (o, e) =>
  835. {
  836. model.Employee = _employees.FirstOrDefault(x => x.ID == assignment.EmployeeLink.ID);
  837. };
  838. appointments.Add(model);
  839. }
  840. private void LoadAssignments(IList<ICalendarAppointment> appointments)
  841. {
  842. foreach (var assignment in _assignments)
  843. LoadAssignment(assignment, appointments);
  844. }
  845. public DataModel DataModel(Selection selection)
  846. {
  847. var ids = _assignments.Select(x => x.ID).ToArray();
  848. return new AutoDataModel<Assignment>(Filter<Assignment>.Where(x => x.ID).InList(ids));
  849. }
  850. private static T CheckGrid<T>([NotNull] ref T? grid) where T : new()
  851. {
  852. grid ??= new T();
  853. return grid;
  854. }
  855. private void ReloadColumns()
  856. {
  857. CalendarControl.Columns = _employees.OrderBy(x => x.Name);
  858. bColumnsLoaded = true;
  859. }
  860. #region Block
  861. private (TimeSpan Start, TimeSpan End)? GetFillBlock(DateTime date, object? column, TimeSpan time)
  862. {
  863. if (column is not Employee employee) return null;
  864. if ((BackgroundType == CalendarBackgroundType.Roster) ||
  865. ((BackgroundType == CalendarBackgroundType.Automatic) && (date >= DateTime.Today)))
  866. {
  867. var roster = RosterUtils.GetRoster(EmployeeSelector.GetRoster(employee.ID), employee.RosterStart, date);
  868. if (roster is null) return null;
  869. var blocks = roster.GetBlocks(date, TimeSpan.Zero, TimeSpan.FromDays(1));
  870. var block = blocks.FirstOrDefault(x => x.Start <= time && time <= x.Finish);
  871. if (block is null) return null;
  872. return (block.Start, block.Finish);
  873. }
  874. else
  875. {
  876. foreach (var timesheet in _timesheets.Where(t => (t.EmployeeLink.ID == employee.ID) && (t.Date == date)))
  877. {
  878. var start = !timesheet.Approved.IsEmpty()
  879. ? timesheet.ApprovedStart
  880. : timesheet.Start;
  881. var finish = !timesheet.Approved.IsEmpty()
  882. ? timesheet.ApprovedFinish
  883. : timesheet.Finish;
  884. if(finish == default)
  885. {
  886. finish = TimeSpan.FromHours(24);
  887. }
  888. if(start <= time && time <= finish)
  889. {
  890. return (start, finish);
  891. }
  892. }
  893. return null;
  894. }
  895. }
  896. private TimeSpan? GetFillBlockStart(DateTime date, object? column, TimeSpan time)
  897. {
  898. var block = GetFillBlock(date, column, time);
  899. return block?.Start;
  900. }
  901. private TimeSpan? GetFillBlockEnd(DateTime date, object? column, TimeSpan time)
  902. {
  903. var block = GetFillBlock(date, column, time);
  904. return block?.End;
  905. }
  906. private void Calendar_BlockHeld(object sender, CalendarBlockEventArgs e)
  907. {
  908. OpenContextMenu(sender, e, false);
  909. }
  910. public enum ContextMenuItems
  911. {
  912. Create,
  913. Fill
  914. }
  915. public CalendarTimeSlot FillSlot(CalendarBlockEventArgs e, CalendarTimeSlot slot)
  916. {
  917. if(CalendarControl.GetEmptySpace(e.Point, out var start, out var end))
  918. {
  919. if(start is null || end is null)
  920. {
  921. var block = GetFillBlock(e.Date, e.Column, CalendarControl.GetTime(e.Point));
  922. return new(slot.EmployeeID, e.Date, start ?? block?.Start ?? e.Start, end ?? block?.End ?? e.End);
  923. }
  924. else
  925. {
  926. return new(slot.EmployeeID, e.Date, start.Value, end.Value);
  927. }
  928. }
  929. else
  930. {
  931. return slot;
  932. }
  933. }
  934. private void Calendar_BlockRightClicked(object sender, CalendarBlockEventArgs e)
  935. {
  936. OpenContextMenu(sender, e, true);
  937. }
  938. private void OpenContextMenu(object sender, CalendarBlockEventArgs e, bool allowFill)
  939. {
  940. object? value;
  941. if(e.Value is AssignmentAppointment appointment)
  942. {
  943. if (appointment.Model.Meeting.Link.ID != Guid.Empty)
  944. {
  945. e.Menu.AddItem("Edit Meeting", null, appointment.Model, EditMeeting);
  946. }
  947. else
  948. {
  949. e.Menu.AddItem("Edit Assignment", null, appointment.Model, EditAssignment);
  950. e.Menu.AddSeparator();
  951. e.Menu.AddItem("Copy Assignment", null, appointment.Model, CopyAssignment);
  952. e.Menu.AddSeparator();
  953. e.Menu.AddItem("Fill Available Time", null, () =>
  954. {
  955. if(CalendarControl.GetEmptySpace(e.Point, out var start, out var end, time: e.Start, exclude: [appointment]))
  956. {
  957. var time = AssignmentType switch
  958. {
  959. CalendarAssignmentType.Automatic or CalendarAssignmentType.Actual => appointment.Model.Actual,
  960. CalendarAssignmentType.Booked => appointment.Model.Booked,
  961. _ => throw new InvalidEnumException<CalendarAssignmentType>(AssignmentType)
  962. };
  963. if(start is null || end is null)
  964. {
  965. var block = GetFillBlock(e.Date, e.Column, e.Start);
  966. time.Start = start ?? block?.Start ?? time.Start;
  967. time.Finish = end ?? block?.End ?? time.Finish;
  968. }
  969. else
  970. {
  971. time.Start = start.Value;
  972. time.Finish = end.Value;
  973. }
  974. Client.Save(appointment.Model, "Adjusted to fill time");
  975. }
  976. });
  977. }
  978. e.Menu.AddSeparatorIfNeeded();
  979. CreateDigitalFormsMenu(e.Menu, appointment);
  980. e.Menu.AddSeparatorIfNeeded();
  981. if (appointment.Model.Meeting.Link.ID != Guid.Empty)
  982. {
  983. e.Menu.AddItem("Delete Meeting", null, appointment.Model, DeleteMeeting);
  984. }
  985. else
  986. {
  987. e.Menu.AddItem("Delete Assignment", null, appointment.Model, DeleteAssignment);
  988. }
  989. value = appointment.Model;
  990. }
  991. else if(e.Value is LeaveRequestAppointment leaveAppointment)
  992. {
  993. if (Security.CanView<LeaveRequest>())
  994. {
  995. e.Menu.AddItem(
  996. Security.CanEdit<LeaveRequest>() ? "Edit Leave" : "View Leave",
  997. null,
  998. leaveAppointment.Model,
  999. EditLeave);
  1000. }
  1001. value = leaveAppointment.Model;
  1002. }
  1003. else if(e.Value is StandardLeaveAppointment standardLeaveAppointment)
  1004. {
  1005. if (Security.CanView<StandardLeave>())
  1006. {
  1007. e.Menu.AddItem(
  1008. Security.CanEdit<StandardLeave>() ? "Edit Standard Leave" : "View Standard Leave",
  1009. null,
  1010. standardLeaveAppointment.Model,
  1011. EditStandardLeave);
  1012. }
  1013. value = standardLeaveAppointment.Model;
  1014. }
  1015. else if (e.Value is null)
  1016. {
  1017. if (e.Column is not Employee employee) return;
  1018. var slot = new CalendarTimeSlot(employee.ID, e.Date, e.Start, e.End);
  1019. var createmenu = e.Menu.AddItem("Create...", null, null)
  1020. .WithName($"Menu_{nameof(ContextMenuItems.Create)}");
  1021. createmenu.AddItem("New Assignment", null, slot, slot => CreateAssignment(slot));
  1022. // createmenu.AddItem("New Meeting", null, slot, CreateMeeting);
  1023. if (allowFill)
  1024. {
  1025. var fillMenu = e.Menu.AddItem("Fill...", null, null)
  1026. .WithName($"Menu_{nameof(ContextMenuItems.Fill)}");
  1027. fillMenu.AddItem("New Assignment", null, () => CreateAssignment(FillSlot(e, slot)));
  1028. // fillMenu.AddItem("New Meeting", null, () => CreateMeeting(FillSlot(e, slot)));
  1029. }
  1030. if (_copiedmodel != null)
  1031. {
  1032. e.Menu.AddSeparator();
  1033. e.Menu.AddItem("Paste Assignment", null, slot, slot => PasteAssignment(slot, _copiedmodel));
  1034. }
  1035. value = slot;
  1036. }
  1037. else
  1038. {
  1039. value = null;
  1040. }
  1041. e.Menu.AddSeparatorIfNeeded();
  1042. e.Menu.AddItem("Zoom In", null, ZoomIn);
  1043. e.Menu.AddItem("Zoom Out", null, ZoomOut, enabled: Zoom > 100);
  1044. e.Menu.AddItem("Reset Zoom", null, ResetZoom);
  1045. CustomiseContextMenu?.Invoke(e.Menu, new CalendarDataMenuEventArgs(value, e));
  1046. }
  1047. private static void CreateDigitalFormsMenu(ContextMenu menu, AssignmentAppointment appointment)
  1048. {
  1049. var digitalForms = menu.AddItem("Digital Forms", null, null);
  1050. DynamicGridUtils.PopulateFormMenu<AssignmentForm, Assignment, AssignmentLink>(
  1051. digitalForms,
  1052. appointment.Model.ID,
  1053. () => new Client<Assignment>().Load(Filter<Assignment>.Where(x => x.ID).IsEqualTo(appointment.Model.ID)).First(),
  1054. false);
  1055. }
  1056. public void CreateMeeting(CalendarTimeSlot slot)
  1057. {
  1058. var meeting = new Meeting
  1059. {
  1060. Date = slot.Date
  1061. };
  1062. meeting.Time.Start = slot.Start;
  1063. meeting.Time.Finish = slot.End;
  1064. ItemCreated?.Invoke(this, new CalendarDataEventArgs(meeting));
  1065. var args = new CalendarHandledEventArgs<Meeting>(meeting);
  1066. ItemEditing?.Invoke(this, args);
  1067. if (args.Status == CalendarHandledStatus.Cancel)
  1068. return;
  1069. if (args.Status == CalendarHandledStatus.Handled)
  1070. {
  1071. Refresh();
  1072. return;
  1073. }
  1074. CheckGrid(ref mg);
  1075. var items = new[] { meeting };
  1076. bool bOK = mg.EditItems(
  1077. items,
  1078. (type) =>
  1079. {
  1080. if (type == typeof(Assignment))
  1081. return LoadMeetingEmployees(slot.EmployeeID);
  1082. else if (type == typeof(MeetingItem))
  1083. return LoadMeetingItems();
  1084. return null;
  1085. },
  1086. true
  1087. );
  1088. if (bOK)
  1089. Refresh();
  1090. }
  1091. private static CoreTable LoadMeetingEmployees(Guid employeeid)
  1092. {
  1093. var result = new CoreTable();
  1094. result.LoadColumns(typeof(Assignment));
  1095. var assignment = new Assignment();
  1096. LookupFactory.DoLookup<Assignment, Employee, EmployeeLink>(assignment, x => x.EmployeeLink, employeeid);
  1097. result.LoadRows([assignment]);
  1098. return result;
  1099. }
  1100. private static CoreTable LoadMeetingItems()
  1101. {
  1102. var result = new CoreTable();
  1103. result.LoadColumns(typeof(MeetingItem));
  1104. return result;
  1105. }
  1106. private void EditMeeting(Assignment model)
  1107. {
  1108. CheckGrid(ref mg);
  1109. if (DynamicGridUtils.EditEntity<Meeting>(model.Meeting.Link.ID, out var meeting))
  1110. {
  1111. ItemChanged?.Invoke(this, new CalendarDataEventArgs(meeting));
  1112. Refresh();
  1113. }
  1114. }
  1115. private void DeleteMeeting(Assignment model)
  1116. {
  1117. if (!MessageWindow.ShowYesNo("Are you sure you wish to delete this meeting?", "Confirm Delete"))
  1118. return;
  1119. var meeting = new Meeting { ID = model.Meeting.Link.ID };
  1120. Client.Delete(meeting, "Meeting Deleted from Scheduler");
  1121. ItemChanged?.Invoke(this, new CalendarDataEventArgs(meeting));
  1122. Refresh();
  1123. SelectionChanged?.Invoke(this, new CalendarDataEventArgs(null));
  1124. }
  1125. public Assignment? CreateAssignment(CalendarTimeSlot slot)
  1126. {
  1127. var ass = new Assignment
  1128. {
  1129. Date = slot.Date
  1130. };
  1131. ass.Booked.Start = slot.Start;
  1132. ass.Booked.Finish = slot.End;
  1133. if ((AssignmentType == CalendarAssignmentType.Actual) || ((AssignmentType == CalendarAssignmentType.Automatic) && (ass.Date <= DateTime.Today)))
  1134. {
  1135. ass.Actual.Start = ass.Booked.Start;
  1136. ass.Actual.Finish = ass.Booked.Finish;
  1137. }
  1138. ass.EmployeeLink.ID = slot.EmployeeID;
  1139. ItemCreated?.Invoke(this, new CalendarDataEventArgs(ass));
  1140. var args = new CalendarHandledEventArgs<Assignment>(ass);
  1141. ItemEditing?.Invoke(this, args);
  1142. if (args.Status == CalendarHandledStatus.Cancel)
  1143. return null;
  1144. if ((args.Status == CalendarHandledStatus.Handled) || CheckGrid(ref ag).EditItems([ass]))
  1145. {
  1146. _assignments.Add(ass);
  1147. LoadAssignment(ass, _appointments);
  1148. _copiedmodel = null;
  1149. }
  1150. return ass;
  1151. }
  1152. private void EditAssignment(Assignment model)
  1153. {
  1154. var grid = CheckGrid(ref ag);
  1155. Client.EnsureColumns(model, grid.LoadEditorColumns());
  1156. if (DynamicGridUtils.EditEntity<Assignment>(model))
  1157. {
  1158. ItemChanged?.Invoke(this, new CalendarDataEventArgs(model));
  1159. // Refresh();
  1160. }
  1161. }
  1162. private void DeleteAssignment(Assignment model)
  1163. {
  1164. if (!MessageWindow.ShowYesNo("Are you sure you wish to delete this assignment?", "Confirm Delete"))
  1165. return;
  1166. var ass = new Assignment { ID = model.ID };
  1167. Client.Delete(ass, "Assignment Deleted from Scheduler");
  1168. ItemChanged?.Invoke(this, new CalendarDataEventArgs(ass));
  1169. Refresh();
  1170. SelectionChanged?.Invoke(this, new CalendarDataEventArgs(null));
  1171. }
  1172. private Assignment? _copiedmodel;
  1173. private void CopyAssignment(Assignment model)
  1174. {
  1175. _copiedmodel = model;
  1176. }
  1177. private Assignment PasteAssignment(CalendarTimeSlot slot, Assignment copied)
  1178. {
  1179. var ass = copied.Clone();
  1180. ass.Date = slot.Date;
  1181. ass.ID = Guid.Empty;
  1182. ass.Number = 0;
  1183. ass.CommitChanges();
  1184. ass.Booked.Start = slot.Start;
  1185. ass.Booked.Finish = slot.End;
  1186. if ((AssignmentType == CalendarAssignmentType.Actual) || ((AssignmentType == CalendarAssignmentType.Automatic) && (ass.Date <= DateTime.Today)))
  1187. {
  1188. ass.Actual.Start = ass.Booked.Start;
  1189. ass.Actual.Finish = ass.Booked.Finish;
  1190. }
  1191. ass.EmployeeLink.ID = slot.EmployeeID;
  1192. ass.EmployeeLink.Clear();
  1193. Client.Save(ass, "");
  1194. _assignments.Add(ass);
  1195. LoadAssignment(ass, _appointments);
  1196. _copiedmodel = null;
  1197. return ass;
  1198. }
  1199. private void EditLeave(LeaveRequest model)
  1200. {
  1201. var grid = CheckGrid(ref lg);
  1202. Client.EnsureColumns(model, grid.LoadEditorColumns());
  1203. if (DynamicGridUtils.EditEntity(model))
  1204. {
  1205. ItemChanged?.Invoke(this, new CalendarDataEventArgs(model));
  1206. Refresh();
  1207. }
  1208. }
  1209. private void EditStandardLeave(StandardLeave model)
  1210. {
  1211. var grid = CheckGrid(ref slg);
  1212. Client.EnsureColumns(model, grid.LoadEditorColumns());
  1213. if (DynamicGridUtils.EditEntity(model))
  1214. {
  1215. ItemChanged?.Invoke(this, new CalendarDataEventArgs(model));
  1216. Refresh();
  1217. }
  1218. }
  1219. private void Calendar_BlockClicked(object sender, CalendarBlockEventArgs e)
  1220. {
  1221. if(e.Value is AssignmentAppointment appointment)
  1222. {
  1223. var args = new CalendarDataEventArgs(appointment.Model);
  1224. SelectionChanged?.Invoke(this, args);
  1225. }
  1226. else
  1227. {
  1228. SelectionChanged?.Invoke(this, new CalendarDataEventArgs(null));
  1229. }
  1230. }
  1231. #endregion
  1232. #region Layout
  1233. private void _settingsButton_OnClick(object sender, RoutedEventArgs e)
  1234. {
  1235. _splitPanel.View = DynamicSplitPanelView.Master;
  1236. }
  1237. private void _splitPanel_OnOnChanged(object sender, DynamicSplitPanelSettings e)
  1238. {
  1239. if (EventSuppressor.IsSet(Suppress.Events))
  1240. return;
  1241. if (e.View == DynamicSplitPanelView.Combined && SettingsVisible != CalendarSettingsVisibility.Visible)
  1242. SettingsVisible = CalendarSettingsVisibility.Visible;
  1243. else if (e.View == DynamicSplitPanelView.Master && SettingsVisible == CalendarSettingsVisibility.Visible)
  1244. SettingsVisible = CalendarSettingsVisibility.Hidden;
  1245. }
  1246. #endregion
  1247. private void TextBlock_SizeChanged(object sender, SizeChangedEventArgs e)
  1248. {
  1249. if (sender is not TextBlock block || block.Tag is not Employee employee) return;
  1250. var display = employee.Name;
  1251. var comps = employee.Name.Split(' ');
  1252. if(block.ActualWidth < 75)
  1253. {
  1254. display = (comps[0].Length > 0 ? comps[0][..1] : "")
  1255. + (comps.Length > 1 ? comps.Skip(1).First()[..1].ToUpper() : "");
  1256. }
  1257. else if(block.ActualWidth < 150)
  1258. {
  1259. display = comps.First() + " " + (comps.Length > 1 ? comps.Skip(1).First()[..1].ToUpper() : "");
  1260. }
  1261. block.Text = display;
  1262. }
  1263. #region Date Selection
  1264. private void Left_Click(object sender, RoutedEventArgs e)
  1265. {
  1266. SelectedDate = SelectedDate.AddDays(CalendarView switch
  1267. {
  1268. CalendarViewType.Day => -1,
  1269. CalendarViewType.WorkWeek or CalendarViewType.Week => -7,
  1270. _ => throw new InvalidEnumException<CalendarViewType>(CalendarView)
  1271. });
  1272. }
  1273. private void Right_Click(object sender, RoutedEventArgs e)
  1274. {
  1275. SelectedDate = SelectedDate.AddDays(CalendarView switch
  1276. {
  1277. CalendarViewType.Day => 1,
  1278. CalendarViewType.WorkWeek or CalendarViewType.Week => 7,
  1279. _ => throw new InvalidEnumException<CalendarViewType>(CalendarView)
  1280. });
  1281. }
  1282. private void Today_Click(object sender, RoutedEventArgs e)
  1283. {
  1284. SelectedDate = DateTime.Today;
  1285. }
  1286. private void AlwaysTodayBox_Checked(object sender, RoutedEventArgs e)
  1287. {
  1288. if (EventSuppressor.IsSet(Suppress.Events)) return;
  1289. Properties.AlwaysStartOnToday = AlwaysTodayBox.IsChecked == true;
  1290. DoSaveSettings();
  1291. }
  1292. #endregion
  1293. }
  1294. }