Calendar.xaml.cs 58 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557
  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; }
  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. // var widthtimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) };
  529. // widthtimer.Tick += (o, e) =>
  530. // {
  531. // if (Bookings.ActualWidth > 0.0F)
  532. // {
  533. // widthtimer.IsEnabled = false;
  534. // ReloadColumns();
  535. // }
  536. // };
  537. // widthtimer.IsEnabled = true;
  538. }
  539. }
  540. public virtual void Shutdown(CancelEventArgs? cancel)
  541. {
  542. }
  543. private void DoSaveSettings()
  544. {
  545. SaveSettings?.Invoke(this, Properties);
  546. }
  547. private bool bFirst = true;
  548. public virtual void Refresh()
  549. {
  550. RefreshData(StartDate, EndDate);
  551. }
  552. private bool _refreshing = false;
  553. private void RefreshData(DateTime startDate, DateTime endDate)
  554. {
  555. if (EventSuppressor.IsSet(Suppress.Refresh))
  556. return;
  557. if (!_refreshing)
  558. {
  559. _refreshing = true;
  560. Dispatcher.BeginInvoke(() => DoRefreshData(startDate, endDate));
  561. }
  562. }
  563. private void DoRefreshData(DateTime startDate, DateTime endDate)
  564. {
  565. _refreshing = false;
  566. using (new WaitCursor())
  567. {
  568. if (!bColumnsLoaded)
  569. ReloadColumns();
  570. var query = new MultiQuery();
  571. var empids = _employees.ToArray(x => x.ID);
  572. if (BackgroundType != CalendarBackgroundType.Roster)
  573. {
  574. query.Add(
  575. Filter<TimeSheet>.Where(x => x.EmployeeLink.ID).InList(empids)
  576. .And(x => x.Date).IsGreaterThanOrEqualTo(startDate)
  577. .And(x => x.Date).IsLessThanOrEqualTo(endDate)
  578. .And(x=>x.LeaveRequestLink.ID).IsEqualTo(Guid.Empty)
  579. .And(x=>x.StandardLeaveLink.ID).IsEqualTo(Guid.Empty),
  580. TimeSheetModel.Columns
  581. );
  582. }
  583. query.Add(
  584. Filter<Assignment>.Where(x => x.EmployeeLink.ID).InList(empids)
  585. .And(x => x.Date).IsGreaterThanOrEqualTo(startDate)
  586. .And(x => x.Date).IsLessThanOrEqualTo(endDate),
  587. AssignmentModel.Columns,
  588. new SortOrder<Assignment>(x => x.EmployeeLink.ID).ThenBy(x => x.Date).ThenBy(x => x.Booked.Duration, SortDirection.Descending)
  589. );
  590. query.Add(
  591. Filter<LeaveRequest>
  592. .Where(x => x.Status).IsNotEqualTo(LeaveRequestStatus.Rejected)
  593. .And(x => x.EmployeeLink.ID).InList(empids)
  594. .And(x => x.From).IsLessThanOrEqualTo(endDate)
  595. .And(x => x.To).IsGreaterThanOrEqualTo(startDate),
  596. Columns.None<LeaveRequest>()
  597. .Add(x => x.ID)
  598. .Add(x => x.EmployeeLink.ID)
  599. .Add(x => x.From)
  600. .Add(x => x.FromTime)
  601. .Add(x => x.To)
  602. .Add(x => x.ToTime)
  603. .Add(x => x.LeaveType.Description)
  604. .Add(x => x.LeaveType.Color)
  605. .Add(x => x.Status)
  606. .Add(x => x.Notes));
  607. query.Add(
  608. Filter<StandardLeave>
  609. .Where(x => x.From).IsLessThanOrEqualTo(endDate)
  610. .And(x => x.To).IsGreaterThanOrEqualTo(startDate),
  611. Columns.None<StandardLeave>()
  612. .Add(x => x.ID)
  613. .Add(c => c.ID)
  614. .Add(c => c.LeaveType.Description)
  615. .Add(c => c.Name)
  616. .Add(c => c.LeaveType.Color)
  617. .Add(c => c.From)
  618. .Add(c => c.FromTime)
  619. .Add(c => c.To)
  620. .Add(c => c.ToTime));
  621. query.Query();
  622. _timesheets = (BackgroundType == CalendarBackgroundType.Roster)
  623. ? []
  624. : query.Get<TimeSheet>().ToArray<TimeSheet>();
  625. _leaverequests = query.Get<LeaveRequest>().ToArray<LeaveRequest>();
  626. _assignments = query.Get<Assignment>().ToList<Assignment>();
  627. _standardleaves = query.Get<StandardLeave>().ToArray<StandardLeave>();
  628. LoadBackground();
  629. _appointments.Clear();
  630. LoadStandardLeaves(_appointments);
  631. LoadLeaveRequests(_appointments);
  632. LoadAssignments(_appointments);
  633. try
  634. {
  635. CalendarControl.Dates = CoreUtils.Range(startDate, endDate, x => x.AddDays(1));
  636. CalendarControl.ItemsSource = _appointments;
  637. }
  638. catch (Exception e)
  639. {
  640. Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
  641. }
  642. bFirst = false;
  643. }
  644. }
  645. public EmployeeRosterItem? GetRoster(Guid employeeid, DateTime date)
  646. {
  647. var employee = _employees.FirstOrDefault(x => x.ID == employeeid);
  648. if (employee is null) return null;
  649. var roster = RosterUtils.GetRoster(EmployeeSelector.GetRoster(employeeid), employee.RosterStart, date);
  650. return roster;
  651. }
  652. public bool GetActiveWindow(Guid employeeid, DateTime date, ref TimeSpan start, ref TimeSpan finish)
  653. {
  654. var result = false;
  655. foreach (var assignment in _assignments.Where(a => (a.EmployeeLink.ID == employeeid) && (a.Date) == date))
  656. {
  657. result = true;
  658. var curstart = AssignmentType switch
  659. {
  660. CalendarAssignmentType.Booked => assignment.Booked.Start,
  661. CalendarAssignmentType.Actual => assignment.Actual.Start,
  662. _ => Assignment.EffectiveTime(assignment.Actual.Start, assignment.Booked.Start)
  663. };
  664. var curfinish = AssignmentType switch
  665. {
  666. CalendarAssignmentType.Booked => assignment.Booked.Finish,
  667. CalendarAssignmentType.Actual => assignment.Actual.Finish,
  668. _ => Assignment.EffectiveTime(
  669. assignment.Actual.Finish,
  670. Assignment.EffectiveTime(assignment.Actual.Start, assignment.Booked.Start)
  671. .Add(assignment.Booked.Duration)
  672. )
  673. };
  674. start = start > curstart ? curstart : start;
  675. finish = finish < curfinish ? curfinish : finish;
  676. }
  677. if ((BackgroundType == CalendarBackgroundType.Roster) ||
  678. ((BackgroundType == CalendarBackgroundType.Automatic) && (date >= DateTime.Today)))
  679. {
  680. var employee = _employees.FirstOrDefault(x => x.ID == employeeid);
  681. if (employee != null)
  682. {
  683. var roster = RosterUtils.GetRoster(EmployeeSelector.GetRoster(employeeid), employee.RosterStart, date);
  684. if (roster != null)
  685. {
  686. var blocks = roster.GetBlocks(date, TimeSpan.MinValue, TimeSpan.MaxValue);
  687. foreach (var block in blocks)
  688. {
  689. start = start > block.Start ? block.Start : start;
  690. finish = finish < block.Finish ? block.Finish : finish;
  691. }
  692. }
  693. }
  694. }
  695. else
  696. {
  697. foreach (var timesheet in _timesheets.Where(t => (t.EmployeeLink.ID == employeeid) && (t.Date == date)))
  698. {
  699. result = true;
  700. var curstart = !timesheet.Approved.IsEmpty()
  701. ? timesheet.ApprovedStart
  702. : timesheet.Start;
  703. var curfinish = !timesheet.Approved.IsEmpty()
  704. ? timesheet.ApprovedFinish
  705. : timesheet.Finish;
  706. start = start > curstart ? curstart : start;
  707. finish = finish < curfinish ? curfinish : finish;
  708. }
  709. }
  710. return result;
  711. }
  712. public Assignment[] GetAssignments(Guid employeeid, DateTime date)
  713. {
  714. return _assignments.Where(a => (a.EmployeeLink.ID == employeeid) && (a.Date == date)).ToArray();
  715. }
  716. private void LoadBackground()
  717. {
  718. var regions = new ObservableCollection<CalendarRegion>();
  719. foreach (var employee in _employees)
  720. {
  721. for (var date = StartDate; date < EndDate; date = date.AddDays(1))
  722. {
  723. if ((BackgroundType == CalendarBackgroundType.Roster) ||
  724. ((BackgroundType == CalendarBackgroundType.Automatic) && (date >= DateTime.Today)))
  725. {
  726. var roster = RosterUtils.GetRoster(EmployeeSelector.GetRoster(employee.ID), employee.RosterStart, date);
  727. if (roster != null)
  728. {
  729. var blocks = roster.GetBlocks(date, TimeSpan.FromSeconds(0), TimeSpan.FromDays(1));
  730. foreach (var block in blocks)
  731. {
  732. regions.Add(new CalendarRegion
  733. {
  734. Date = date,
  735. Column = employee,
  736. Start = block.Start,
  737. End = block.Finish,
  738. Background = Colors.Yellow.ToBrush(0.3)
  739. });
  740. }
  741. }
  742. }
  743. else
  744. {
  745. foreach (var timesheet in _timesheets.Where(t => (t.EmployeeLink.ID == employee.ID) && (t.Date == date)))
  746. {
  747. var start = !timesheet.Approved.IsEmpty()
  748. ? timesheet.ApprovedStart
  749. : timesheet.Start;
  750. var finish = !timesheet.Approved.IsEmpty()
  751. ? timesheet.ApprovedFinish
  752. : timesheet.Finish;
  753. if(finish == default)
  754. {
  755. finish = TimeSpan.FromHours(24);
  756. }
  757. regions.Add(new CalendarRegion
  758. {
  759. Date = date,
  760. Column = employee,
  761. Start = start,
  762. End = finish,
  763. Background = (!timesheet.Approved.IsEmpty() ? Colors.LightGreen : Colors.LightSalmon).ToBrush(0.4),
  764. });
  765. }
  766. }
  767. }
  768. }
  769. CalendarControl.Regions = regions;
  770. }
  771. private void LoadStandardLeaves(IList<ICalendarAppointment> appointments)
  772. {
  773. for (var date = StartDate; date < EndDate; date = date.AddDays(1))
  774. {
  775. var leaves = _standardleaves.Where(x =>
  776. (x.From <= date)
  777. && (x.To.Add(x.ToTime) > date)
  778. ).ToArray();
  779. foreach (var leave in leaves)
  780. {
  781. foreach (var employee in _employees)
  782. {
  783. var start = (date.Date == leave.From.Date) ? leave.FromTime : TimeSpan.FromSeconds(0);
  784. var finish = (date.Date == leave.To.Date) ? leave.ToTime : TimeSpan.FromDays(1).Subtract(TimeSpan.FromSeconds(1));
  785. var roster = RosterUtils.GetRoster(EmployeeSelector.GetRoster(employee.ID), employee.RosterStart, date);
  786. if (roster != null)
  787. {
  788. var blocks = roster.GetBlocks(date, start, finish);
  789. foreach (var block in blocks)
  790. {
  791. var appt = new StandardLeaveAppointment(leave, employee, block);
  792. appointments.Add(appt);
  793. }
  794. }
  795. }
  796. }
  797. }
  798. }
  799. private void LoadLeaveRequests(IList<ICalendarAppointment> appointments)
  800. {
  801. for (var date = StartDate; date < EndDate; date = date.AddDays(1))
  802. {
  803. var ids = _employees.ToArray(x => x.ID);
  804. var leaves = _leaverequests.Where(x =>
  805. (x.From <= date)
  806. && (x.To.Add(x.ToTime) > date)
  807. && ids.Contains(x.EmployeeLink.ID)
  808. ).ToArray();
  809. foreach (var leave in leaves)
  810. {
  811. var employee = _employees.FirstOrDefault(x => x.ID == leave.EmployeeLink.ID);
  812. if (employee is null) return;
  813. var roster = RosterUtils.GetRoster(EmployeeSelector.GetRoster(employee.ID), employee.RosterStart, date);
  814. if (roster != null)
  815. {
  816. var start = (date.Date == leave.From.Date) ? leave.FromTime : TimeSpan.FromSeconds(0);
  817. var finish = (date.Date == leave.To.Date) ? leave.ToTime : TimeSpan.FromDays(1).Subtract(TimeSpan.FromSeconds(1));
  818. var blocks = roster.GetBlocks(date, start, finish);
  819. foreach(var block in blocks)
  820. {
  821. appointments.Add(new LeaveRequestAppointment(leave, employee, block));
  822. }
  823. }
  824. }
  825. }
  826. }
  827. private HashSet<Assignment> _savingAssignments = new();
  828. private void LoadAssignment(Assignment assignment, IList<ICalendarAppointment> appointments)
  829. {
  830. var employee = _employees.FirstOrDefault(x => x.ID == assignment.EmployeeLink.ID);
  831. if (employee is null) return;
  832. var model = new AssignmentAppointment(assignment, employee, AssignmentType);
  833. model.OnUpdate += () =>
  834. {
  835. if (_savingAssignments.Add(assignment))
  836. {
  837. Dispatcher.BeginInvoke(() =>
  838. {
  839. _savingAssignments.Remove(assignment);
  840. Client.SaveAsync(assignment, "Edited by user").LogIfFail();
  841. });
  842. }
  843. };
  844. model.EmployeeChanged += (o, e) =>
  845. {
  846. model.Employee = _employees.FirstOrDefault(x => x.ID == assignment.EmployeeLink.ID);
  847. };
  848. appointments.Add(model);
  849. }
  850. private void LoadAssignments(IList<ICalendarAppointment> appointments)
  851. {
  852. foreach (var assignment in _assignments)
  853. LoadAssignment(assignment, appointments);
  854. }
  855. public DataModel DataModel(Selection selection)
  856. {
  857. var ids = _assignments.Select(x => x.ID).ToArray();
  858. return new AutoDataModel<Assignment>(Filter<Assignment>.Where(x => x.ID).InList(ids));
  859. }
  860. private static T CheckGrid<T>([NotNull] ref T? grid) where T : new()
  861. {
  862. grid ??= new T();
  863. return grid;
  864. }
  865. private void ReloadColumns()
  866. {
  867. CalendarControl.Columns = _employees.OrderBy(x => x.Name);
  868. bColumnsLoaded = true;
  869. }
  870. #region Block
  871. private (TimeSpan Start, TimeSpan End)? GetFillBlock(DateTime date, object? column, TimeSpan time)
  872. {
  873. if (column is not Employee employee) return null;
  874. if ((BackgroundType == CalendarBackgroundType.Roster) ||
  875. ((BackgroundType == CalendarBackgroundType.Automatic) && (date >= DateTime.Today)))
  876. {
  877. var roster = RosterUtils.GetRoster(EmployeeSelector.GetRoster(employee.ID), employee.RosterStart, date);
  878. if (roster is null) return null;
  879. var blocks = roster.GetBlocks(date, TimeSpan.Zero, TimeSpan.FromDays(1));
  880. var block = blocks.FirstOrDefault(x => x.Start <= time && time <= x.Finish);
  881. if (block is null) return null;
  882. return (block.Start, block.Finish);
  883. }
  884. else
  885. {
  886. foreach (var timesheet in _timesheets.Where(t => (t.EmployeeLink.ID == employee.ID) && (t.Date == date)))
  887. {
  888. var start = !timesheet.Approved.IsEmpty()
  889. ? timesheet.ApprovedStart
  890. : timesheet.Start;
  891. var finish = !timesheet.Approved.IsEmpty()
  892. ? timesheet.ApprovedFinish
  893. : timesheet.Finish;
  894. if(finish == default)
  895. {
  896. finish = TimeSpan.FromHours(24);
  897. }
  898. if(start <= time && time <= finish)
  899. {
  900. return (start, finish);
  901. }
  902. }
  903. return null;
  904. }
  905. }
  906. private TimeSpan? GetFillBlockStart(DateTime date, object? column, TimeSpan time)
  907. {
  908. var block = GetFillBlock(date, column, time);
  909. return block?.Start;
  910. }
  911. private TimeSpan? GetFillBlockEnd(DateTime date, object? column, TimeSpan time)
  912. {
  913. var block = GetFillBlock(date, column, time);
  914. return block?.End;
  915. }
  916. private void Calendar_BlockHeld(object sender, CalendarBlockEventArgs e)
  917. {
  918. Calendar_BlockRightClicked(sender, e);
  919. }
  920. public enum ContextMenuItems
  921. {
  922. Create,
  923. Fill
  924. }
  925. public CalendarTimeSlot FillSlot(CalendarBlockEventArgs e, CalendarTimeSlot slot)
  926. {
  927. if(CalendarControl.GetEmptySpace(e.Point, out var start, out var end))
  928. {
  929. if(start is null || end is null)
  930. {
  931. var block = GetFillBlock(e.Date, e.Column, CalendarControl.GetTime(e.Point));
  932. return new(slot.EmployeeID, e.Date, start ?? block?.Start ?? e.Start, end ?? block?.End ?? e.End);
  933. }
  934. else
  935. {
  936. return new(slot.EmployeeID, e.Date, start.Value, end.Value);
  937. }
  938. }
  939. else
  940. {
  941. return slot;
  942. }
  943. }
  944. private void Calendar_BlockRightClicked(object sender, CalendarBlockEventArgs e)
  945. {
  946. object? value;
  947. if(e.Value is AssignmentAppointment appointment)
  948. {
  949. if (appointment.Model.Meeting.Link.ID != Guid.Empty)
  950. {
  951. e.Menu.AddItem("Edit Meeting", null, appointment.Model, EditMeeting);
  952. }
  953. else
  954. {
  955. e.Menu.AddItem("Edit Assignment", null, appointment.Model, EditAssignment);
  956. e.Menu.AddSeparator();
  957. e.Menu.AddItem("Copy Assignment", null, appointment.Model, CopyAssignment);
  958. e.Menu.AddSeparator();
  959. e.Menu.AddItem("Fill Available Time", null, () =>
  960. {
  961. if(CalendarControl.GetEmptySpace(e.Point, out var start, out var end, time: e.Start, exclude: [appointment]))
  962. {
  963. var time = AssignmentType switch
  964. {
  965. CalendarAssignmentType.Automatic or CalendarAssignmentType.Actual => appointment.Model.Actual,
  966. CalendarAssignmentType.Booked => appointment.Model.Booked,
  967. _ => throw new InvalidEnumException<CalendarAssignmentType>(AssignmentType)
  968. };
  969. if(start is null || end is null)
  970. {
  971. var block = GetFillBlock(e.Date, e.Column, e.Start);
  972. time.Start = start ?? block?.Start ?? time.Start;
  973. time.Finish = end ?? block?.End ?? time.Finish;
  974. }
  975. else
  976. {
  977. time.Start = start.Value;
  978. time.Finish = end.Value;
  979. }
  980. Client.Save(appointment.Model, "Adjusted to fill time");
  981. }
  982. });
  983. }
  984. e.Menu.AddSeparatorIfNeeded();
  985. CreateDigitalFormsMenu(e.Menu, appointment);
  986. e.Menu.AddSeparatorIfNeeded();
  987. if (appointment.Model.Meeting.Link.ID != Guid.Empty)
  988. {
  989. e.Menu.AddItem("Delete Meeting", null, appointment.Model, DeleteMeeting);
  990. }
  991. else
  992. {
  993. e.Menu.AddItem("Delete Assignment", null, appointment.Model, DeleteAssignment);
  994. }
  995. value = appointment.Model;
  996. }
  997. else if(e.Value is LeaveRequestAppointment leaveAppointment)
  998. {
  999. if (Security.CanView<LeaveRequest>())
  1000. {
  1001. e.Menu.AddItem(
  1002. Security.CanEdit<LeaveRequest>() ? "Edit Leave" : "View Leave",
  1003. null,
  1004. leaveAppointment.Model,
  1005. EditLeave);
  1006. }
  1007. value = leaveAppointment.Model;
  1008. }
  1009. else if(e.Value is StandardLeaveAppointment standardLeaveAppointment)
  1010. {
  1011. if (Security.CanView<StandardLeave>())
  1012. {
  1013. e.Menu.AddItem(
  1014. Security.CanEdit<StandardLeave>() ? "Edit Standard Leave" : "View Standard Leave",
  1015. null,
  1016. standardLeaveAppointment.Model,
  1017. EditStandardLeave);
  1018. }
  1019. value = standardLeaveAppointment.Model;
  1020. }
  1021. else if (e.Value is null)
  1022. {
  1023. if (e.Column is not Employee employee) return;
  1024. var slot = new CalendarTimeSlot(employee.ID, e.Date, e.Start, e.End);
  1025. var createmenu = e.Menu.AddItem("Create...", null, null)
  1026. .WithName($"Menu_{nameof(ContextMenuItems.Create)}");
  1027. createmenu.AddItem("New Assignment", null, slot, slot => CreateAssignment(slot));
  1028. // createmenu.AddItem("New Meeting", null, slot, CreateMeeting);
  1029. var fillMenu = e.Menu.AddItem("Fill...", null, null)
  1030. .WithName($"Menu_{nameof(ContextMenuItems.Fill)}");
  1031. fillMenu.AddItem("New Assignment", null, () => CreateAssignment(FillSlot(e, slot)));
  1032. // fillMenu.AddItem("New Meeting", null, () => CreateMeeting(FillSlot(e, slot)));
  1033. if (_copiedmodel != null)
  1034. {
  1035. e.Menu.AddSeparator();
  1036. e.Menu.AddItem("Paste Assignment", null, slot, slot => PasteAssignment(slot, _copiedmodel));
  1037. }
  1038. value = slot;
  1039. }
  1040. else
  1041. {
  1042. value = null;
  1043. }
  1044. e.Menu.AddSeparatorIfNeeded();
  1045. e.Menu.AddItem("Zoom In", null, ZoomIn);
  1046. e.Menu.AddItem("Zoom Out", null, ZoomOut, enabled: Zoom > 100);
  1047. e.Menu.AddItem("Reset Zoom", null, ResetZoom);
  1048. CustomiseContextMenu?.Invoke(e.Menu, new CalendarDataMenuEventArgs(value, e));
  1049. }
  1050. private static void CreateDigitalFormsMenu(ContextMenu menu, AssignmentAppointment appointment)
  1051. {
  1052. var digitalForms = menu.AddItem("Digital Forms", null, null);
  1053. DynamicGridUtils.PopulateFormMenu<AssignmentForm, Assignment, AssignmentLink>(
  1054. digitalForms,
  1055. appointment.Model.ID,
  1056. () => new Client<Assignment>().Load(Filter<Assignment>.Where(x => x.ID).IsEqualTo(appointment.Model.ID)).First(),
  1057. false);
  1058. }
  1059. public void CreateMeeting(CalendarTimeSlot slot)
  1060. {
  1061. var meeting = new Meeting
  1062. {
  1063. Date = slot.Date
  1064. };
  1065. meeting.Time.Start = slot.Start;
  1066. meeting.Time.Finish = slot.End;
  1067. ItemCreated?.Invoke(this, new CalendarDataEventArgs(meeting));
  1068. var args = new CalendarHandledEventArgs<Meeting>(meeting);
  1069. ItemEditing?.Invoke(this, args);
  1070. if (args.Status == CalendarHandledStatus.Cancel)
  1071. return;
  1072. if (args.Status == CalendarHandledStatus.Handled)
  1073. {
  1074. Refresh();
  1075. return;
  1076. }
  1077. CheckGrid(ref mg);
  1078. var items = new[] { meeting };
  1079. bool bOK = mg.EditItems(
  1080. items,
  1081. (type) =>
  1082. {
  1083. if (type == typeof(Assignment))
  1084. return LoadMeetingEmployees(slot.EmployeeID);
  1085. else if (type == typeof(MeetingItem))
  1086. return LoadMeetingItems();
  1087. return null;
  1088. },
  1089. true
  1090. );
  1091. if (bOK)
  1092. Refresh();
  1093. }
  1094. private static CoreTable LoadMeetingEmployees(Guid employeeid)
  1095. {
  1096. var result = new CoreTable();
  1097. result.LoadColumns(typeof(Assignment));
  1098. var assignment = new Assignment();
  1099. LookupFactory.DoLookup<Assignment, Employee, EmployeeLink>(assignment, x => x.EmployeeLink, employeeid);
  1100. result.LoadRows([assignment]);
  1101. return result;
  1102. }
  1103. private static CoreTable LoadMeetingItems()
  1104. {
  1105. var result = new CoreTable();
  1106. result.LoadColumns(typeof(MeetingItem));
  1107. return result;
  1108. }
  1109. private void EditMeeting(Assignment model)
  1110. {
  1111. CheckGrid(ref mg);
  1112. if (DynamicGridUtils.EditEntity<Meeting>(model.Meeting.Link.ID, out var meeting))
  1113. {
  1114. ItemChanged?.Invoke(this, new CalendarDataEventArgs(meeting));
  1115. Refresh();
  1116. }
  1117. }
  1118. private void DeleteMeeting(Assignment model)
  1119. {
  1120. if (!MessageWindow.ShowYesNo("Are you sure you wish to delete this meeting?", "Confirm Delete"))
  1121. return;
  1122. var meeting = new Meeting { ID = model.Meeting.Link.ID };
  1123. Client.Delete(meeting, "Meeting Deleted from Scheduler");
  1124. ItemChanged?.Invoke(this, new CalendarDataEventArgs(meeting));
  1125. Refresh();
  1126. SelectionChanged?.Invoke(this, new CalendarDataEventArgs(null));
  1127. }
  1128. public Assignment? CreateAssignment(CalendarTimeSlot slot)
  1129. {
  1130. var ass = new Assignment
  1131. {
  1132. Date = slot.Date
  1133. };
  1134. ass.Booked.Start = slot.Start;
  1135. ass.Booked.Finish = slot.End;
  1136. if ((AssignmentType == CalendarAssignmentType.Actual) || ((AssignmentType == CalendarAssignmentType.Automatic) && (ass.Date <= DateTime.Today)))
  1137. {
  1138. ass.Actual.Start = ass.Booked.Start;
  1139. ass.Actual.Finish = ass.Booked.Finish;
  1140. }
  1141. ass.EmployeeLink.ID = slot.EmployeeID;
  1142. ItemCreated?.Invoke(this, new CalendarDataEventArgs(ass));
  1143. var args = new CalendarHandledEventArgs<Assignment>(ass);
  1144. ItemEditing?.Invoke(this, args);
  1145. if (args.Status == CalendarHandledStatus.Cancel)
  1146. return null;
  1147. if ((args.Status == CalendarHandledStatus.Handled) || CheckGrid(ref ag).EditItems([ass]))
  1148. {
  1149. _assignments.Add(ass);
  1150. LoadAssignment(ass, _appointments);
  1151. _copiedmodel = null;
  1152. }
  1153. return ass;
  1154. }
  1155. private void EditAssignment(Assignment model)
  1156. {
  1157. var grid = CheckGrid(ref ag);
  1158. Client.EnsureColumns(model, grid.LoadEditorColumns());
  1159. if (DynamicGridUtils.EditEntity<Assignment>(model))
  1160. {
  1161. ItemChanged?.Invoke(this, new CalendarDataEventArgs(model));
  1162. // Refresh();
  1163. }
  1164. }
  1165. private void DeleteAssignment(Assignment model)
  1166. {
  1167. if (!MessageWindow.ShowYesNo("Are you sure you wish to delete this assignment?", "Confirm Delete"))
  1168. return;
  1169. var ass = new Assignment { ID = model.ID };
  1170. Client.Delete(ass, "Assignment Deleted from Scheduler");
  1171. ItemChanged?.Invoke(this, new CalendarDataEventArgs(ass));
  1172. Refresh();
  1173. SelectionChanged?.Invoke(this, new CalendarDataEventArgs(null));
  1174. }
  1175. private Assignment? _copiedmodel;
  1176. private void CopyAssignment(Assignment model)
  1177. {
  1178. _copiedmodel = model;
  1179. }
  1180. private Assignment PasteAssignment(CalendarTimeSlot slot, Assignment copied)
  1181. {
  1182. var ass = copied.Clone();
  1183. ass.Date = slot.Date;
  1184. ass.ID = Guid.Empty;
  1185. ass.Number = 0;
  1186. ass.CommitChanges();
  1187. ass.Booked.Start = slot.Start;
  1188. ass.Booked.Finish = slot.End;
  1189. if ((AssignmentType == CalendarAssignmentType.Actual) || ((AssignmentType == CalendarAssignmentType.Automatic) && (ass.Date <= DateTime.Today)))
  1190. {
  1191. ass.Actual.Start = ass.Booked.Start;
  1192. ass.Actual.Finish = ass.Booked.Finish;
  1193. }
  1194. ass.EmployeeLink.ID = slot.EmployeeID;
  1195. ass.EmployeeLink.Clear();
  1196. Client.Save(ass, "");
  1197. _assignments.Add(ass);
  1198. LoadAssignment(ass, _appointments);
  1199. _copiedmodel = null;
  1200. return ass;
  1201. }
  1202. private void EditLeave(LeaveRequest model)
  1203. {
  1204. var grid = CheckGrid(ref lg);
  1205. Client.EnsureColumns(model, grid.LoadEditorColumns());
  1206. if (DynamicGridUtils.EditEntity(model))
  1207. {
  1208. ItemChanged?.Invoke(this, new CalendarDataEventArgs(model));
  1209. Refresh();
  1210. }
  1211. }
  1212. private void EditStandardLeave(StandardLeave model)
  1213. {
  1214. var grid = CheckGrid(ref slg);
  1215. Client.EnsureColumns(model, grid.LoadEditorColumns());
  1216. if (DynamicGridUtils.EditEntity(model))
  1217. {
  1218. ItemChanged?.Invoke(this, new CalendarDataEventArgs(model));
  1219. Refresh();
  1220. }
  1221. }
  1222. private void Calendar_BlockClicked(object sender, CalendarBlockEventArgs e)
  1223. {
  1224. if(e.Value is AssignmentAppointment appointment)
  1225. {
  1226. var args = new CalendarDataEventArgs(appointment.Model);
  1227. SelectionChanged?.Invoke(this, args);
  1228. }
  1229. else
  1230. {
  1231. SelectionChanged?.Invoke(this, new CalendarDataEventArgs(null));
  1232. }
  1233. }
  1234. #endregion
  1235. #region Layout
  1236. private void _settingsButton_OnClick(object sender, RoutedEventArgs e)
  1237. {
  1238. _splitPanel.View = DynamicSplitPanelView.Master;
  1239. }
  1240. private void _splitPanel_OnOnChanged(object sender, DynamicSplitPanelSettings e)
  1241. {
  1242. if (EventSuppressor.IsSet(Suppress.Events))
  1243. return;
  1244. if (e.View == DynamicSplitPanelView.Combined && SettingsVisible != CalendarSettingsVisibility.Visible)
  1245. SettingsVisible = CalendarSettingsVisibility.Visible;
  1246. else if (e.View == DynamicSplitPanelView.Master && SettingsVisible == CalendarSettingsVisibility.Visible)
  1247. SettingsVisible = CalendarSettingsVisibility.Hidden;
  1248. }
  1249. #endregion
  1250. private void TextBlock_SizeChanged(object sender, SizeChangedEventArgs e)
  1251. {
  1252. if (sender is not TextBlock block || block.Tag is not Employee employee) return;
  1253. var display = employee.Name;
  1254. var comps = employee.Name.Split(' ');
  1255. if(block.ActualWidth < 75)
  1256. {
  1257. display = (comps[0].Length > 0 ? comps[0][..1] : "")
  1258. + (comps.Length > 1 ? comps.Skip(1).First()[..1].ToUpper() : "");
  1259. }
  1260. else if(block.ActualWidth < 150)
  1261. {
  1262. display = comps.First() + " " + (comps.Length > 1 ? comps.Skip(1).First()[..1].ToUpper() : "");
  1263. }
  1264. block.Text = display;
  1265. }
  1266. #region Date Selection
  1267. private void Left_Click(object sender, RoutedEventArgs e)
  1268. {
  1269. SelectedDate = SelectedDate.AddDays(CalendarView switch
  1270. {
  1271. CalendarViewType.Day => -1,
  1272. CalendarViewType.WorkWeek or CalendarViewType.Week => -7,
  1273. _ => throw new InvalidEnumException<CalendarViewType>(CalendarView)
  1274. });
  1275. }
  1276. private void Right_Click(object sender, RoutedEventArgs e)
  1277. {
  1278. SelectedDate = SelectedDate.AddDays(CalendarView switch
  1279. {
  1280. CalendarViewType.Day => 1,
  1281. CalendarViewType.WorkWeek or CalendarViewType.Week => 7,
  1282. _ => throw new InvalidEnumException<CalendarViewType>(CalendarView)
  1283. });
  1284. }
  1285. private void Today_Click(object sender, RoutedEventArgs e)
  1286. {
  1287. SelectedDate = DateTime.Today;
  1288. }
  1289. private void AlwaysTodayBox_Checked(object sender, RoutedEventArgs e)
  1290. {
  1291. if (EventSuppressor.IsSet(Suppress.Events)) return;
  1292. Properties.AlwaysStartOnToday = AlwaysTodayBox.IsChecked == true;
  1293. DoSaveSettings();
  1294. }
  1295. #endregion
  1296. }
  1297. }