using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Globalization; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Threading; using Comal.Classes; using InABox.Clients; using InABox.Configuration; using InABox.Core; using InABox.DynamicGrid; using InABox.WPF; using Org.BouncyCastle.Asn1.X509.Qualified; using PRS.Shared; using Syncfusion.SfSkinManager; using Syncfusion.UI.Xaml.Scheduler; using Selection = InABox.Core.Selection; using SelectionChangedEventArgs = System.Windows.Controls.SelectionChangedEventArgs; namespace PRSDesktop { public class SfScheduler2 : SfScheduler { public static readonly DependencyProperty HeaderVisibilityProperty = DependencyProperty.Register( nameof(HeaderVisibility), typeof(Visibility), typeof(SfScheduler2), new UIPropertyMetadata(Visibility.Collapsed) ); public Visibility HeaderVisibility { get => (Visibility)GetValue(HeaderVisibilityProperty); set { SetValue(HeaderVisibilityProperty,value); SetHeaderVisibility(); } } private void SetHeaderVisibility() { if (GetTemplateChild("PART_ScheduleHeaderControl") is SchedulerHeaderControl cell) cell.Visibility = HeaderVisibility; } public override void OnApplyTemplate() { base.OnApplyTemplate(); SetHeaderVisibility(); } } public partial class Calendar { private enum Suppress { Selector, // Prevent the Selector from Being Changed Calendar, // Prevent the Calendar from Being Reconfigured Events, // Prevent the Selectors from Responding to Events Refresh, // Stop the Data from Being refreshed Settings // Dont allow settings to be updated } private EventSuppressor suppressor = null; public void DisableUpdate() { if (suppressor == null) suppressor = new EventSuppressor(Suppress.Refresh, Suppress.Settings); } public void EnableUpdate() { if (suppressor != null) { suppressor.Dispose(); suppressor = null; } } private void DoSetValue(DependencyProperty property, T value, Action? updateselector, Action? updateinterface) { SetValue(property, value); if (!EventSuppressor.IsSet(Suppress.Selector) && (updateselector != null)) using (new EventSuppressor(Suppress.Events)) updateselector(); if (!EventSuppressor.IsSet(Suppress.Calendar) && (updateinterface != null)) updateinterface(); if (!EventSuppressor.IsSet(Suppress.Refresh)) Refresh(); if (!EventSuppressor.IsSet(Suppress.Settings)) { Properties.SettingsVisible = SettingsVisible; Properties.Date = SelectedDate; Properties.StartHour = (int)Bookings.DaysViewSettings.StartHour; Properties.EndHour = (int)Bookings.DaysViewSettings.EndHour; Properties.CalendarView = CalendarView; Properties.EmployeeSelector = EmployeeSettings; Properties.EmployeeSelection = EmployeeSelection; Properties.TimeInterval = TimeInterval; Properties.AssignmentType = AssignmentType; Properties.BackgroundType = BackgroundType; Properties.Zoom = Zoom; SaveSettings?.Invoke(this, Properties); } } #region HeaderVisibility Dependency Property public static readonly DependencyProperty HeaderVisibilityProperty = DependencyProperty.Register( nameof(HeaderVisibility), typeof(Visibility), typeof(Calendar), new UIPropertyMetadata(Visibility.Collapsed) ); public Visibility HeaderVisibility { get => (Visibility)GetValue(HeaderVisibilityProperty); set { SetValue(HeaderVisibilityProperty,value); Bookings.HeaderVisibility = value; } } #endregion #region SettingsVisible Dependency Property public static readonly DependencyProperty SettingsVisibleProperty = DependencyProperty.Register( nameof(SettingsVisible), typeof(CalendarSettingsVisibility), typeof(Calendar), new UIPropertyMetadata(CalendarSettingsVisibility.Disabled) ); public CalendarSettingsVisibility SettingsVisible { get => (CalendarSettingsVisibility)GetValue(SettingsVisibleProperty); set => SetSettingsVisibility(value); } private void SetSettingsVisibility(CalendarSettingsVisibility value) { DoSetValue( SettingsVisibleProperty, value, () => { }, () => { _splitPanel.View = value == CalendarSettingsVisibility.Visible ? DynamicSplitPanelView.Combined : DynamicSplitPanelView.Master; _splitPanel.AllowableViews = value == CalendarSettingsVisibility.Disabled ? DynamicSplitPanelView.Master : DynamicSplitPanelView.Master | DynamicSplitPanelView.Combined; ResizeColumns(this.ActualWidth); } ); } #endregion #region CalendarView Dependency Property public static readonly DependencyProperty CalendarViewProperty = DependencyProperty.Register( nameof(CalendarView), typeof(CalendarViewType), typeof(Calendar), new UIPropertyMetadata(CalendarViewType.Day) ); public CalendarViewType CalendarView { get => (CalendarViewType)GetValue(CalendarViewProperty); set => SetCalendarView(value); } private void SetCalendarView(CalendarViewType value) { DoSetValue( CalendarViewProperty, value, () => CalendarViewSelector.SelectedIndex = (int)value, () => { Bookings.ViewType = value switch { CalendarViewType.Day => SchedulerViewType.Day, CalendarViewType.WorkWeek => SchedulerViewType.WorkWeek, CalendarViewType.Week => SchedulerViewType.Week, _ => SchedulerViewType.Day }; ResizeColumns(this.ActualWidth); } ); } private void CalendarViewSelector_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (EventSuppressor.IsSet(Suppress.Events)) return; using (new EventSuppressor(Suppress.Selector)) SetCalendarView((CalendarViewType)CalendarViewSelector.SelectedIndex); } #endregion #region EmployeeSelector Dependency Property public static readonly DependencyProperty EmployeeSelectionProperty = DependencyProperty.Register( nameof(EmployeeSelection), typeof(EmployeeSelectorData), typeof(Calendar), new UIPropertyMetadata(new EmployeeSelectorData()) ); public EmployeeSelectorData EmployeeSelection { get => (EmployeeSelectorData)GetValue(EmployeeSelectionProperty); set => SetEmployeeSelection(value); } private void SetEmployeeSelection(EmployeeSelectorData value) { DoSetValue( EmployeeSelectionProperty, value, () => EmployeeSelector.Selection = value, () => { _employees = EmployeeSelector.GetEmployeeData((row, rosters) => new EmployeeResourceModel(row, rosters)); ReloadColumns(); } ); } private void EmployeeSelector_OnSelectionChanged(object sender, EmployeeSelectorSelectionChangedArgs args) { if (EventSuppressor.IsSet(Suppress.Events)) return; using (new EventSuppressor(Suppress.Selector)) SetEmployeeSelection(args.Selection); } #endregion #region EmployeeSettings Dependency Property public static readonly DependencyProperty EmployeeSettingsProperty = DependencyProperty.Register( nameof(EmployeeSettings), typeof(EmployeeSelectorSettings), typeof(Calendar), new UIPropertyMetadata(new EmployeeSelectorSettings()) ); public EmployeeSelectorSettings EmployeeSettings { get => (EmployeeSelectorSettings)GetValue(EmployeeSettingsProperty); set => SetEmployeeSettings(value); } private void SetEmployeeSettings(EmployeeSelectorSettings value) { DoSetValue( EmployeeSettingsProperty, value, () => EmployeeSelector.Settings = value, () => { // Nothing to do here } ); } private void EmployeeSelector_OnSettingsChanged(object sender, EmployeeSelectorSettingsChangedArgs args) { if (EventSuppressor.IsSet(Suppress.Events)) return; using (new EventSuppressor(Suppress.Selector)) SetEmployeeSettings(args.Settings); } #endregion #region TimeInterval DependencyProperty public static readonly DependencyProperty TimeIntervalProperty = DependencyProperty.Register( "TimeInterval", typeof(CalendarTimeInterval), typeof(Calendar), new PropertyMetadata(CalendarTimeInterval.FifteenMinutes) ); public CalendarTimeInterval TimeInterval { get => (CalendarTimeInterval)GetValue(TimeIntervalProperty); set => SetTimeInterval(value); } private void SetTimeInterval(CalendarTimeInterval value) { DoSetValue( TimeIntervalProperty, value, () => IntervalSelector.SelectedIndex = (int)value, () => { Bookings.DaysViewSettings.TimeInterval = TimeIntervalToTimeSpan(value); UpdateZoom(); }); } private void IntervalSelector_OnSelectionChanged(object sender, SelectionChangedEventArgs e) { if (EventSuppressor.IsSet(Suppress.Events)) return; using (new EventSuppressor(Suppress.Selector)) SetTimeInterval((CalendarTimeInterval)IntervalSelector.SelectedIndex); } public TimeSpan TimeIntervalToTimeSpan(CalendarTimeInterval interval) { return interval switch { CalendarTimeInterval.FiveMinutes => new TimeSpan(0, 5, 0), CalendarTimeInterval.SixMinutes => new TimeSpan(0, 6, 0), CalendarTimeInterval.TenMinutes => new TimeSpan(0, 10, 0), CalendarTimeInterval.FifteenMinutes => new TimeSpan(0, 15, 0), CalendarTimeInterval.TwentyMinutes => new TimeSpan(0, 20, 0), CalendarTimeInterval.ThirtyMinutes => new TimeSpan(0, 30, 0), _ => new TimeSpan(1, 0, 0) }; } public int BlocksPerHour(CalendarTimeInterval interval) { return interval switch { CalendarTimeInterval.FiveMinutes => 12, CalendarTimeInterval.SixMinutes => 10, CalendarTimeInterval.TenMinutes => 6, CalendarTimeInterval.FifteenMinutes => 4, CalendarTimeInterval.TwentyMinutes => 3, CalendarTimeInterval.ThirtyMinutes => 2, _ => 1 }; } private TimeSpan AdjustStartTime(TimeSpan time) { long blocksize = TimeIntervalToTimeSpan(TimeInterval).Ticks; //long blocksperday = TimeSpan.FromDays(1).Ticks / ; long blocknumber = time.Ticks / blocksize; long blockstart = blocknumber * blocksize; return TimeSpan.FromTicks(blockstart); } #endregion #region SelectedDate Dependency Property public static readonly DependencyProperty SelectedDateProperty = DependencyProperty.Register( nameof(SelectedDate), typeof(DateTime), typeof(Calendar), new UIPropertyMetadata(DateTime.Today) ); public DateTime SelectedDate { get => (DateTime)GetValue(SelectedDateProperty); set => SetSelectedDate(value); } private void SetSelectedDate(DateTime value) { DoSetValue( SelectedDateProperty, value, () => { /* DateSelector.Date = value; */ }, () => { Bookings.DisplayDate = value; Bookings.SelectedDate = value; }); } // private void DateSelector_DateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) // { // if (EventSuppressor.IsSet(Suppress.Events)) // return; // using (new EventSuppressor(Suppress.Selector)) // SetSelectedDate(DateSelector.Date); // } public DateTime StartDate => (Properties?.CalendarView ?? CalendarViewType.Day) == CalendarViewType.Day ? SelectedDate : SelectedDate.StartOfWeek(DayOfWeek.Monday); public DateTime EndDate => Properties.CalendarView == CalendarViewType.Day ? StartDate.AddDays(1) : Properties.CalendarView == CalendarViewType.WorkWeek ? StartDate.AddDays(5) : StartDate.AddDays(7); #endregion #region StartHour Dependency Properties public static readonly DependencyProperty StartHourProperty = DependencyProperty.Register( nameof(StartHour), typeof(int), typeof(Calendar), new UIPropertyMetadata(6) ); public int StartHour { get => (int)GetValue(StartHourProperty); set => SetStartHour(value); } private void SetStartHour(int value) { value = Math.Min(EndHour-1,Math.Max(0, value)); DoSetValue( StartHourProperty, value, () => StartTimeSelector.Text = FormatHour(value), () => { Bookings.DaysViewSettings.StartHour = value; UpdateZoom(); }); } private void StartTimeSelector_Down_Click(object sender, RoutedEventArgs e) { if (EventSuppressor.IsSet(Suppress.Events)) return; SetStartHour(StartHour - 1); } private void StartTimeSelector_Up_Click(object sender, RoutedEventArgs e) { if (EventSuppressor.IsSet(Suppress.Events)) return; SetStartHour(StartHour + 1); } #endregion #region End Hour Property public static readonly DependencyProperty EndHourProperty = DependencyProperty.Register( nameof(EndHour), typeof(int), typeof(Calendar), new UIPropertyMetadata(18) ); public int EndHour { get => (int)GetValue(EndHourProperty); set => SetEndHour(value); } private void SetEndHour(int value) { value = Math.Max(StartHour + 1, Math.Min(24, value)); DoSetValue( EndHourProperty, value, () => FinishTimeSelector.Text = FormatHour(value), () => { Bookings.DaysViewSettings.EndHour = value; UpdateZoom(); }); } private void FinishTimeSelector_Down_Click(object sender, RoutedEventArgs e) { if (EventSuppressor.IsSet(Suppress.Events)) return; SetEndHour(EndHour - 1); } private void FinishTimeSelector_Up_Click(object sender, RoutedEventArgs e) { if (EventSuppressor.IsSet(Suppress.Events)) return; SetEndHour(EndHour + 1); } private string FormatHour(int hour) { return hour <= 0 || hour >= 24 ? "Midnight" : hour < 12 ? string.Format("{0}:00 AM", hour) : hour > 12 ? string.Format("{0}:00 PM", hour) : "12:00 NN"; } #endregion #region AssignmentType Dependency Property public static readonly DependencyProperty AssignmentTypeProperty = DependencyProperty.Register( nameof(AssignmentType), typeof(CalendarAssignmentType), typeof(Calendar), new UIPropertyMetadata(CalendarAssignmentType.Booked) ); public CalendarAssignmentType AssignmentType { get => (CalendarAssignmentType)GetValue(AssignmentTypeProperty); set => SetAssignmentType(value); } private void SetAssignmentType(CalendarAssignmentType value) { DoSetValue( AssignmentTypeProperty, value, () => AssignmentTypeSelector.SelectedIndex = (int)value, null ); } private void AssignmentTypeSelector_OnSelectionChanged(object sender, SelectionChangedEventArgs e) { if (EventSuppressor.IsSet(Suppress.Events)) return; using (new EventSuppressor(Suppress.Selector)) SetAssignmentType((CalendarAssignmentType)AssignmentTypeSelector.SelectedIndex); } #endregion #region BackgroundType Dependency Property public static readonly DependencyProperty BackgroundTypeProperty = DependencyProperty.Register( nameof(BackgroundType), typeof(CalendarBackgroundType), typeof(Calendar), new UIPropertyMetadata(CalendarBackgroundType.Roster) ); public CalendarBackgroundType BackgroundType { get => (CalendarBackgroundType)GetValue(BackgroundTypeProperty); set => SetBackgroundType(value); } private void SetBackgroundType(CalendarBackgroundType type) { DoSetValue( BackgroundTypeProperty, type, () => BackgroundTypeSelector.SelectedIndex = (int)type, null ); } private void BackgroundTypeSelector_OnSelectionChanged(object sender, SelectionChangedEventArgs e) { if (EventSuppressor.IsSet(Suppress.Events)) return; using (new EventSuppressor(Suppress.Selector)) SetBackgroundType((CalendarBackgroundType)BackgroundTypeSelector.SelectedIndex); } #endregion #region Zoom Dependency Properties public static readonly DependencyProperty ZoomProperty = DependencyProperty.Register( nameof(Zoom), typeof(double), typeof(Calendar), new UIPropertyMetadata((double)100F) ); public double Zoom { get => (double)GetValue(ZoomProperty); set => SetZoom(value); } private void SetZoom(double value) { DoSetValue( ZoomProperty, value, () => ZoomSelector.Text = $"{value:F0}%", () => UpdateZoom() ); } private void UpdateZoom() { if (double.IsNaN(this.ActualHeight) || (this.ActualHeight == 0.0F)) return; var blocksize = (this.ActualHeight - (Bookings.DaysViewSettings.ViewHeaderHeight + Bookings.DaysViewSettings.ResourceHeaderSize + 2.0F)) / ((EndHour - StartHour) * this.BlocksPerHour(TimeInterval)); Bookings.DaysViewSettings.TimeIntervalSize = (double)Zoom * blocksize / 100.0F; } private void ZoomSelector_Down_Click(object sender, RoutedEventArgs e) { if (EventSuppressor.IsSet(Suppress.Events)) return; ZoomOut(); } private void ZoomSelector_Up_Click(object sender, RoutedEventArgs e) { if (EventSuppressor.IsSet(Suppress.Events)) return; ZoomIn(); } public void ZoomIn() => SetZoom(Zoom * 1.125F); public void ZoomOut() => SetZoom(Zoom / 1.125F); public void ResetZoom() => SetZoom(100.0F); #endregion #region Event Handlers public event LoadSettings LoadSettings; public event SaveSettings SaveSettings; public CalendarConfigurationEvent ConfigurationChanged; public event CalendarDataEvent CustomiseContextMenu; public event CalendarDataEvent SelectionChanged; public event CalendarDataEvent ItemCreated; public event CalendarDataEvent ItemChanged; public event CalendarHandledEvent ItemEditing; #endregion public void SelectEmployee(Guid employeeid) => EmployeeSelector.SelectEmployee(employeeid); // Populated as requiew when EmployeeSelector.SelectionChanged is triggered private EmployeeResourceModel[] _employees = new EmployeeResourceModel[] { }; // Populated once at startup private StandardLeaveModel[] _standardleaves = new StandardLeaveModel[] { }; private LeaveRequestModel[] _leaverequests = new LeaveRequestModel[] { }; // Populated on each Refresh private TimeSheetModel[] _timesheets = new TimeSheetModel[] { }; // Populated on each Refresh private List _assignments = new List(); private bool bColumnsLoaded; private AssignmentGrid ag; private DynamicDataGrid mg; public bool IsReady { get; set; } public CalendarSettings Properties { get; set; } //public SfScheduler2? Bookings => this.FindName("_bookings") as SfScheduler2; public Calendar() { using (EventSuppressor.All()) { InitializeComponent(); SetValue(StartHourProperty, 0); SetValue(EndHourProperty, 24); SfSkinManager.SetTheme(Bookings, new Theme() { ThemeName = "Office2019White", ScrollBarMode = ScrollBarMode.Compact }); } } public virtual void Setup() { using (new EventSuppressor(Suppress.Settings, Suppress.Refresh, Suppress.Events)) { Properties = LoadSettings?.Invoke(this) ?? new CalendarSettings(); SetCalendarView(Properties.CalendarView); SetSettingsVisibility(Properties.SettingsVisible); SetSelectedDate(Properties.AlwaysStartOnToday ? DateTime.Today : Properties.Date); SetStartHour(Properties.StartHour); SetEndHour(Properties.EndHour); SetTimeInterval(Properties.TimeInterval); SetAssignmentType(Properties.AssignmentType); SetBackgroundType(Properties.BackgroundType); SetCalendarView(Properties.CalendarView); SetZoom(Properties.Zoom); EmployeeSelector.Setup(); SetEmployeeSettings(Properties.EmployeeSelector); SetEmployeeSelection(Properties.EmployeeSelection); AlwaysTodayBox.IsChecked = Properties.AlwaysStartOnToday; _employees = EmployeeSelector.GetEmployeeData((row, rosters) => new EmployeeResourceModel(row, rosters)); MultiQuery query = new MultiQuery(); query.Add( new Filter(x=>x.Status).IsNotEqualTo(LeaveRequestStatus.Rejected), LeaveRequestModel.Columns ); query.Add( null, StandardLeaveModel.Columns ); query.Query(); _standardleaves = query.Get().Rows.Select(row => new StandardLeaveModel(row)).ToArray(); _leaverequests = query.Get().Rows.Select(row => new LeaveRequestModel(row)).ToArray(); var widthtimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) }; widthtimer.Tick += (o, e) => { if (Bookings.ActualWidth > 0.0F) { widthtimer.IsEnabled = false; ReloadColumns(); } }; widthtimer.IsEnabled = true; } } public virtual void Shutdown(CancelEventArgs? cancel) { } private bool bFirst = true; public virtual void Refresh() { RefreshData(StartDate, EndDate); } private void RefreshData(DateTime startDate, DateTime endDate) { if (EventSuppressor.IsSet(Suppress.Refresh)) return; using (new WaitCursor()) { if (!bColumnsLoaded) ReloadColumns(); MultiQuery query = new MultiQuery(); var empids = _employees.Select(x => x.ID).ToArray(); if (BackgroundType != CalendarBackgroundType.Roster) { query.Add( new Filter(x => x.EmployeeLink.ID).InList(empids) .And(x => x.Date).IsGreaterThanOrEqualTo(startDate) .And(x => x.Date).IsLessThanOrEqualTo(endDate) .And(x=>x.LeaveRequestLink.ID).IsEqualTo(Guid.Empty) .And(x=>x.StandardLeaveLink.ID).IsEqualTo(Guid.Empty), TimeSheetModel.Columns ); } query.Add( new Filter(x => x.EmployeeLink.ID).InList(empids) .And(x => x.Date).IsGreaterThanOrEqualTo(startDate) .And(x => x.Date).IsLessThanOrEqualTo(endDate), AssignmentModel.Columns, new SortOrder(x => x.EmployeeLink.ID).ThenBy(x => x.Date).ThenBy(x => x.Booked.Duration, SortDirection.Descending) ); query.Query(); _timesheets = (BackgroundType == CalendarBackgroundType.Roster) ? new TimeSheetModel[] { } : query.Get().Rows.Select(r => new TimeSheetModel(r)).ToArray(); _assignments = query.Get().Rows.Select(r => new AssignmentModel(r)).ToList(); LoadBackground(); var appointments = new CalendarAppointments(); LoadStandardLeaves(appointments); LoadLeaveRequests(appointments); LoadAssignments(appointments); try { Bookings.DisplayDate = Bookings.SelectedDate.HasValue ? Bookings.SelectedDate.Value : StartDate; Bookings.ItemsSource = appointments; } catch (Exception e) { Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace)); } bFirst = false; } } public EmployeeRosterItem GetRoster(Guid employeeid, DateTime date) { var employee = _employees.FirstOrDefault(x => x.ID == employeeid); var roster = RosterUtils.GetRoster(employee?.Roster, employee?.RosterStart, date); return roster; } public bool GetActiveWindow(Guid employeeid, DateTime date, ref TimeSpan start, ref TimeSpan finish) { bool result = false; foreach (var assignment in _assignments.Where(a => (a.EmployeeID == employeeid) && (a.Date) == date)) { result = true; var curstart = AssignmentType switch { CalendarAssignmentType.Booked => assignment.BookedStart, CalendarAssignmentType.Actual => assignment.ActualStart, _ => Assignment.EffectiveTime(assignment.ActualStart, assignment.BookedStart) }; var curfinish = AssignmentType switch { CalendarAssignmentType.Booked => assignment.BookedFinish, CalendarAssignmentType.Actual => assignment.ActualFinish, _ => Assignment.EffectiveTime( assignment.ActualFinish, Assignment.EffectiveTime(assignment.ActualStart, assignment.BookedStart) .Add(assignment.BookedDuration) ) }; start = start > curstart ? curstart : start; finish = finish < curfinish ? curfinish : finish; } if ((BackgroundType == CalendarBackgroundType.Roster) || ((BackgroundType == CalendarBackgroundType.Automatic) && (date >= DateTime.Today))) { var employee = _employees.FirstOrDefault(x => x.ID == employeeid); if (employee != null) { var roster = RosterUtils.GetRoster(employee.Roster, employee.RosterStart, date); if (roster != null) { var blocks = roster.GetBlocks(date, TimeSpan.MinValue, TimeSpan.MaxValue); foreach (var block in blocks) { start = start > block.Start ? block.Start : start; finish = finish < block.Finish ? block.Finish : finish; } } } } else { foreach (var timesheet in _timesheets.Where(t => (t.EmployeeID == employeeid) && (t.Date == date))) { result = true; var curstart = !timesheet.Approved.IsEmpty() ? timesheet.ApprovedStart : timesheet.Start; var curfinish = !timesheet.Approved.IsEmpty() ? timesheet.ApprovedFinish : timesheet.Finish; start = start > curstart ? curstart : start; finish = finish < curfinish ? curfinish : finish; } } return result; } public CoreRow[] GetAssignments(Guid employeeid, DateTime date) { return _assignments.Where(a => (a.Row != null) && (a.EmployeeID == employeeid) && (a.Date == date)).Select(x => x.Row!).ToArray(); } public void UpdateAssignment(Assignment assignment) { void UpdateCalendar( TEntity entity, List collection, Action refresh ) where TEntity : Entity where TModel : class, IModel { if (Bookings.ItemsSource is CalendarAppointments appointments) { var appointment = appointments.FirstOrDefault(x => (Guid)x.Id == entity.ID) as ScheduleAppointment; if (appointment != null) { appointments.Remove(appointment); var calappt = appointment as ICalendarAppointment; if (calappt != null) collection.Remove(calappt.Model); } var table = new CoreTable(); table.LoadColumns(typeof(TEntity)); var row = table.NewRow(); table.FillRow(row, entity); var model = (Activator.CreateInstance(typeof(TModel),row) as TModel)!; collection.Add(model); refresh(model); } } if (Bookings.ItemsSource is CalendarAppointments appointments) UpdateCalendar(assignment, _assignments, (model) => LoadAssignment(model,appointments)); } private void LoadBackground() { var regions = new ObservableCollection(); foreach (var resource in Bookings.ResourceCollection) { var sEmpID = (((SchedulerResource)resource).Id as string) ?? ""; var empid = Guid.Parse(sEmpID); var employee = _employees.FirstOrDefault(x => x.ID == empid); for (var date = StartDate; date < EndDate; date = date.AddDays(1)) { if ((BackgroundType == CalendarBackgroundType.Roster) || ((BackgroundType == CalendarBackgroundType.Automatic) && (date >= DateTime.Today))) { var roster = RosterUtils.GetRoster(employee?.Roster, employee?.RosterStart, date); if (roster != null) { var blocks = roster.GetBlocks(date, TimeSpan.FromSeconds(0), TimeSpan.FromDays(1)); foreach (var block in blocks) { regions.Add( new SpecialTimeRegion { StartTime = date.Add(block.Start), EndTime = date.Add(block.Finish.Subtract(TimeSpan.FromSeconds(1))), ResourceIdCollection = new ObservableCollection { ((SchedulerResource)resource).Id }, Background = new SolidColorBrush(Colors.Yellow) {Opacity = 0.3}, Foreground = new SolidColorBrush(Colors.Black), Text = "" } ); } } } else { foreach (var timesheet in _timesheets.Where(t => (t.EmployeeID == empid) && (t.Date == date))) { var start = !timesheet.Approved.IsEmpty() ? timesheet.ApprovedStart : timesheet.Start; var finish = !timesheet.Approved.IsEmpty() ? timesheet.ApprovedFinish : timesheet.Finish; regions.Add( new SpecialTimeRegion { StartTime = date.Add(start), EndTime = date.Add(finish), ResourceIdCollection = new ObservableCollection { ((SchedulerResource)resource).Id }, Background = new SolidColorBrush(!timesheet.Approved.IsEmpty() ? Colors.LightGreen : Colors.LightSalmon) { Opacity = 0.4 }, Foreground = new SolidColorBrush(Colors.Transparent), Text = "", CanMergeAdjacentRegions = false } ); } } } } Bookings.DaysViewSettings.SpecialTimeRegions = regions; } private void LoadStandardLeaves(CalendarAppointments appointments) { for (var date = StartDate; date < EndDate; date = date.AddDays(1)) { var leaves = _standardleaves.Where(x => (x.From <= date) && (x.To.Add(x.ToTime) > date) ).ToArray(); foreach (var leave in leaves) { foreach (var resource in Bookings.ResourceCollection) { var sEmpID = ((SchedulerResource)resource).Id as string; var empid = Guid.Parse(sEmpID); var employee = _employees.FirstOrDefault(x => x.ID == empid); var start = (date.Date == leave.From.Date) ? leave.FromTime : TimeSpan.FromSeconds(0); var finish = (date.Date == leave.To.Date) ? leave.ToTime : TimeSpan.FromDays(1).Subtract(TimeSpan.FromSeconds(1)); var roster = RosterUtils.GetRoster(employee?.Roster, employee?.RosterStart, date); if (roster != null) { var blocks = roster.GetBlocks(date, start, finish); foreach (var block in blocks) { var appt = new StandardLeaveAppointment(leave, _ => empid, x => x.Color, block); appointments.Add(appt); } } } } } } private void LoadLeaveRequests(CalendarAppointments appointments) { for (var date = StartDate; date < EndDate; date = date.AddDays(1)) { var ids = Bookings.ResourceCollection .Cast() .Where(x=>!String.IsNullOrWhiteSpace(x.Id as String)) .Select(x => Guid.Parse(x.Id.ToString() ?? "")); var leaves = _leaverequests.Where(x => (x.From <= date) && (x.To.Add(x.ToTime) > date) && ids.Contains(x.EmployeeID) ).ToArray(); foreach (var leave in leaves) { var employee = _employees.FirstOrDefault(x => x.ID == leave.EmployeeID); var roster = RosterUtils.GetRoster(employee?.Roster, employee?.RosterStart, date); if (roster != null) { var start = (date.Date == leave.From.Date) ? leave.FromTime : TimeSpan.FromSeconds(0); var finish = (date.Date == leave.To.Date) ? leave.ToTime : TimeSpan.FromDays(1).Subtract(TimeSpan.FromSeconds(1)); var blocks = roster.GetBlocks(date, start, finish); foreach (var block in blocks) { var appt = new LeaveRequestAppointment(leave, x=>x.EmployeeID, x => x.Color, block); appointments.Add(appt); } } } } } private void LoadAssignment(AssignmentModel assignment, CalendarAppointments appointments) { var model = new AssignmentAppointment(assignment, x => x.EmployeeID, x => x.Color, AssignmentType); appointments.Add(model); } private void LoadAssignments(CalendarAppointments appointments) { foreach (var assignment in _assignments) LoadAssignment(assignment, appointments); } public DataModel DataModel(Selection selection) { var ids = _assignments.Select(x => x.ID).ToArray(); return new AutoDataModel(new Filter(x => x.ID).InList(ids)); } // private void ResizeIntervals(double height) // { // if (Bookings.FindVisualChildren().Any()) // { // // if (height > 95 && Bookings.DaysViewSettings.EndHour - Bookings.DaysViewSettings.StartHour > 0) // { // double scrollheight = _employees.Length * 75 > Bookings.ActualWidth ? 15.0F : 0.0F; // var header = _employees.Length > 1 ? 93.0F + scrollheight : 50.0F; // Bookings.DaysViewSettings.TimeIntervalSize = // (height - header) / ((Bookings.DaysViewSettings.EndHour - Bookings.DaysViewSettings.StartHour) * 2.0F); // } // } // // // var scrollers = Bookings.FindVisualChildren().Where(x => string.Equals(x.Name, "PART_TimeSlotScrollViewer")).ToArray(); // // foreach (var scroll in scrollers) // // scroll.VerticalScrollBarVisibility = ScrollBarVisibility.Hidden; // // } private void Schedule_SizeChanged(object sender, SizeChangedEventArgs e) { //ResizeIntervals(e.NewSize.Height); ResizeColumns(e.NewSize.Width); } private void ResizeColumns(double width) { if (double.IsNaN(width) || !Bookings.FindVisualChildren().Any()) return; var maxcount = (int)width / 75; Bookings.DaysViewSettings.VisibleResourceCount = Math.Min(maxcount, _employees.Length * (Bookings.ViewType == SchedulerViewType.Day ? 1 : Bookings.ViewType == SchedulerViewType.WorkWeek ? 5 : 7)); if (Bookings.ResourceCollection is ObservableCollection resources) { var colwidth = GetResourceColumnWidth(); foreach (var emp in _employees) { var resource = resources.FirstOrDefault(x => String.Equals(x.Id?.ToString(), emp.ID.ToString())); if (resource != null) { var comps = emp.Name.Split(' '); var display = emp.Name; if (colwidth < 75) display = string.Format("{0}{1}", comps[0].Length > 0 ? comps[0][..1] : "", comps.Length > 1 ? comps.Skip(1).First()[..1].ToUpper() : ""); else if (colwidth < 150) display = string.Format("{0} {1}", comps.First(), comps.Length > 1 ? comps.Skip(1).First()[..1].ToUpper() : ""); resource.Name = display; } } } } private T CheckGrid(ref T grid) where T : new() { if (grid == null) grid = new T(); return grid; } private void ReloadColumns() { ResizeColumns(Bookings.ActualWidth); var colwidth = GetResourceColumnWidth(); var resources = new List(); foreach (var emp in _employees) { var comps = emp.Name.Split(' '); var display = emp.Name; if (colwidth < 75) display = CoreUtils.Codify(emp.Name); else if (colwidth < 150) display = string.Format("{0} {1}", comps.Length > 0 ? comps.First() : "", comps.Length > 1 ? comps.Skip(1).First().Substring(0, 1).ToUpper() : ""); resources.Add(new SchedulerResource { Name = display, Id = emp.ID.ToString() }); } var sorted = new ObservableCollection(); foreach (var resource in resources.OrderBy(x => x.Name)) sorted.Add(resource); try { Bookings.DaysViewSettings.ResourceHeaderSize = sorted.Count <= 1 ? 0 : 45; } catch (Exception e) { Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace)); } //ResizeIntervals(Bookings.ActualHeight); try { Bookings.ResourceCollection = sorted; } catch (Exception e) { Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace)); } bColumnsLoaded = true; } // private SchedulerResource GetCurrentResource() // { // var p = Mouse.GetPosition(Bookings); // var panels = Bookings.FindVisualChildren().FirstOrDefault(x => string.Equals(x.Name, "PART_ViewHeaderScrollViewer")); // var resource = (int)((p.X + panels.HorizontalOffset - (Bookings.DaysViewSettings.TimeRulerSize + 20F)) / GetResourceColumnWidth()); // return (Bookings.ResourceCollection as Collection)[resource]; // } // // private DateTime GetCurrentTime() // { // var p = Mouse.GetPosition(Bookings); // var hours = (p.Y - (_employees.Length > 1 ? 95.0F : 50.0F)) / (Bookings.DaysViewSettings.TimeIntervalSize * 2F) + // Bookings.DaysViewSettings.StartHour; // var result = Bookings.SelectedDate.Value + TimeSpan.FromHours(hours); // return result; // } private double GetResourceColumnWidth() { var colcount = Math.Max(1, _employees.Length); colcount = colcount * (Bookings.ViewType == SchedulerViewType.Day ? 1 : Bookings.ViewType == SchedulerViewType.Day ? 5 : 7); var colwidth = Bookings.ActualWidth / colcount; var minwidth = (Bookings.ActualWidth - (Bookings.DaysViewSettings.TimeRulerSize + 20F)) / Bookings.DaysViewSettings.VisibleResourceCount; return Math.Max(minwidth, colwidth); } private void Schedule_AppointmentEditorOpening(object sender, AppointmentEditorOpeningEventArgs e) { e.Cancel = true; } private Dictionary _menuitems = new Dictionary(); public MenuItem? FindMenu(CalendarMenuName name) { _menuitems.TryGetValue(name, out MenuItem result); return result; } MenuItem CreateMenu(ItemsControl menu, CalendarMenuName name, string header, Action? action = null, T? data = null) where T : class { var item = new MenuItem(); item.Name = name.ToString(); item.Header = header; if (action != null) item.Click += (o,args) => action(data); item.IsEnabled = data != null; menu.Items.Add(item); _menuitems[name] = item; return item; } private void Bookings_OnSchedulerContextMenuOpening(object? sender, SchedulerContextMenuOpeningEventArgs e) { e.ContextMenu.Items.Clear(); if ((e.MenuType == SchedulerContextMenuType.Appointment) && (e.MenuInfo.Appointment is AssignmentAppointment appointment)) { if (appointment.Model.MeetingID != Guid.Empty) CreateMenu(e.ContextMenu, CalendarMenuName.Edit, "Edit Meeting", EditMeeting, appointment.Model); else { CreateMenu(e.ContextMenu, CalendarMenuName.Edit, "Edit Assignment", EditAssignment, appointment.Model); e.ContextMenu.Items.Add(new Separator()); CreateMenu(e.ContextMenu, CalendarMenuName.Copy, "Copy Assignment", CopyAssignment, appointment.Model); e.ContextMenu.Items.Add(new Separator()); CreateMenu(e.ContextMenu, CalendarMenuName.Fill, "Fill Available Time", FillAssignment, appointment.Model); } e.ContextMenu.Items.Add(new Separator()); CreateDigitalFormsMenu(e.ContextMenu, appointment); if (appointment.Model.MeetingID != Guid.Empty) CreateMenu(e.ContextMenu, CalendarMenuName.Edit, "Delete Meeting", DeleteMeeting, appointment.Model); else CreateMenu(e.ContextMenu, CalendarMenuName.Delete, "Delete Assignment", DeleteAssignment, appointment.Model); e.ContextMenu.Items.Add(new Separator()); CreateMenu(e.ContextMenu, CalendarMenuName.ZoomIn, "Zoom In", (data) => ZoomIn(), null); CreateMenu(e.ContextMenu, CalendarMenuName.ZoomOut, "Zoom Out", (data) => ZoomOut(), null); CreateMenu(e.ContextMenu, CalendarMenuName.ResetZoom, "Reset Zoom", (data) => ResetZoom(), null); CustomiseContextMenu?.Invoke(e.ContextMenu, new CalendarDataEventArgs((e.MenuInfo.Appointment as ICalendarAppointment)?.Model)); } else if (e.MenuType == SchedulerContextMenuType.TimeSlotCell) { if (Guid.TryParse(e.MenuInfo.Resource.Id?.ToString(), out Guid employeeid)) { var slot = new CalendarTimeSlot(employeeid, e.MenuInfo.DateTime.Value); var createmenu = new MenuItem() { Header = "Create.." }; CreateMenu(createmenu, CalendarMenuName.CreateNew, "New Assignment", (data) => CreateAssignment(slot), slot); CreateMenu(createmenu, CalendarMenuName.CreateNew, "New Meeting", (data) => CreateMeeting(slot), slot); e.ContextMenu.Items.Add(createmenu); if (_copiedmodel != null) { e.ContextMenu.Items.Add(new Separator()); CreateMenu(e.ContextMenu, CalendarMenuName.Paste, "Paste Assignment", (data) => PasteAssignment(slot), slot); } e.ContextMenu.Items.Add(new Separator()); CreateMenu(e.ContextMenu, CalendarMenuName.ZoomIn, "Zoom In", (data) => ZoomIn(), slot); CreateMenu(e.ContextMenu, CalendarMenuName.ZoomOut, "Zoom Out", (data) => ZoomOut(), slot); CreateMenu(e.ContextMenu, CalendarMenuName.ResetZoom, "Reset Zoom", (data) => ResetZoom(), slot); CustomiseContextMenu?.Invoke(e.ContextMenu, new CalendarDataEventArgs(slot)); } } } private static void CreateDigitalFormsMenu(ContextMenu menu, AssignmentAppointment appointment) { var digitalForms = new MenuItem { Header = "Digital Forms" }; DynamicGridUtils.PopulateFormMenu( digitalForms, appointment.Model.ID, () => new Client().Load(new Filter(x => x.ID).IsEqualTo(appointment.Model.ID)).First(), false); menu.Items.Add(digitalForms); menu.Items.Add(new Separator()); } public void CreateMeeting(CalendarTimeSlot slot) { if (slot == null) { MessageBox.Show("Please select an employee first!"); return; } var meeting = new Meeting(); meeting.Date = slot.Time.Date; meeting.Time.Start = AdjustStartTime(slot.Time.TimeOfDay); meeting.Time.Duration = TimeIntervalToTimeSpan(TimeInterval); meeting.Time.Finish = meeting.Time.Start + meeting.Time.Duration; ItemCreated?.Invoke(this, new CalendarDataEventArgs(meeting)); var args = new CalendarHandledEventArgs(meeting); ItemEditing?.Invoke(this, args); if (args.Status == CalendarHandledStatus.Cancel) return; if (args.Status == CalendarHandledStatus.Handled) { Refresh(); return; } CheckGrid(ref mg); var items = new[] { meeting }; bool bOK = mg.EditItems( items, (type) => { if (type == typeof(Assignment)) return LoadMeetingEmployees(meeting, slot.EmployeeID); else if (type == typeof(MeetingItem)) return LoadMeetingItems(); return null; }, true ); if (bOK) Refresh(); } private CoreTable LoadMeetingEmployees(Meeting meeting, Guid employeeid) { var result = new CoreTable(); result.LoadColumns(typeof(Assignment)); var assignment = new Assignment(); assignment.EmployeeLink.ID = employeeid; var emp = new Client().Load(new Filter(x => x.ID).IsEqualTo(employeeid)).FirstOrDefault(); if (emp != null) assignment.EmployeeLink.Synchronise(emp); result.LoadRows(new[] { assignment }); return result; } private CoreTable LoadMeetingItems() { var result = new CoreTable(); result.LoadColumns(typeof(MeetingItem)); return result; } private void EditMeeting(AssignmentModel model) { if (model == null) { MessageBox.Show("Please select an entry first!"); return; } CheckGrid(ref mg); var meeting = new Client().Query( new Filter(x => x.ID).IsEqualTo(model.MeetingID), mg.LoadEditorColumns() ).Rows.FirstOrDefault()?.ToObject(); if ((meeting != null) && (mg.EditItems(new[] { meeting }))) { ItemChanged?.Invoke(this, new CalendarDataEventArgs(meeting)); Refresh(); } } private void DeleteMeeting(AssignmentModel model) { if (model == null) { MessageBox.Show("Please select an entry first!"); return; } if (MessageBox.Show("Are you sure you wish to delete this meeting?", "Confirm Delete", MessageBoxButton.YesNo) != MessageBoxResult.Yes) return; var meeting = new Meeting { ID = model.MeetingID }; new Client().Delete(meeting, "Meeting Deleted from Scheduler"); ItemChanged?.Invoke(this, new CalendarDataEventArgs(meeting)); Refresh(); SelectionChanged?.Invoke(this, new CalendarDataEventArgs(null)); } public Assignment CreateAssignment(CalendarTimeSlot slot) { if (slot == null) { MessageBox.Show("Please select an employee first!"); return null; } var ass = new Assignment(); ass.Date = slot.Time.Date; ass.Booked.Start = AdjustStartTime(slot.Time.TimeOfDay); ass.Booked.Duration = TimeIntervalToTimeSpan(TimeInterval); ass.Booked.Finish = ass.Booked.Start + ass.Booked.Duration; if ((AssignmentType == CalendarAssignmentType.Actual) || ((AssignmentType == CalendarAssignmentType.Automatic) && (ass.Date <= DateTime.Today))) { ass.Actual.Start = ass.Booked.Start; ass.Actual.Duration = ass.Booked.Duration; ass.Actual.Finish = ass.Actual.Start + ass.Actual.Duration; } ass.EmployeeLink.ID = slot.EmployeeID; ItemCreated?.Invoke(this, new CalendarDataEventArgs(ass)); var args = new CalendarHandledEventArgs(ass); ItemEditing?.Invoke(this, args); if (args.Status == CalendarHandledStatus.Cancel) return null; if ((args.Status == CalendarHandledStatus.Handled) || CheckGrid(ref ag).EditItems(new[] { ass })) { UpdateAssignment(ass); _copiedmodel = null; } return ass; } private void FillAssignment(AssignmentModel model) { MessageBox.Show("Not Yet Implemented"); } private void EditAssignment(AssignmentModel model) { if (model == null) { MessageBox.Show("Please select an entry first!"); return; } var grid = CheckGrid(ref ag); var ass = new Client().Query(new Filter(x => x.ID).IsEqualTo(model.ID), grid.LoadEditorColumns()) .ToObjects().FirstOrDefault(); if (grid.EditItems(new[] { ass })) { ItemChanged?.Invoke(this, new CalendarDataEventArgs(ass)); Refresh(); } } private void DeleteAssignment(AssignmentModel model) { if (model == null) { MessageBox.Show("Please select an entry first!"); return; } if (MessageBox.Show("Are you sure you wish to delete this assignment?", "Confirm Delete", MessageBoxButton.YesNo) != MessageBoxResult.Yes) return; var ass = new Assignment { ID = model.ID }; new Client().Delete(ass, "Assignment Deleted from Scheduler"); ItemChanged?.Invoke(this, new CalendarDataEventArgs(ass)); Refresh(); SelectionChanged?.Invoke(this, new CalendarDataEventArgs(null)); } private AssignmentModel _copiedmodel; private void CopyAssignment(AssignmentModel model) { if (model == null) { MessageBox.Show("Please select an entry first!"); return; } _copiedmodel = model; } private Assignment PasteAssignment(CalendarTimeSlot slot) { if (slot == null) { MessageBox.Show("Please select an employee first!"); return null; } var ass = _assignments.FirstOrDefault(a => a.ID == _copiedmodel.ID)?.Row?.ToObject(); if (ass == null) { MessageBox.Show("Cannot find copied entry!"); return null; } ass.Date = SelectedDate.Date; ass.ID = Guid.Empty; ass.Number = 0; ass.CommitChanges(); ass.Booked.Start = AdjustStartTime(slot.Time.TimeOfDay); ass.Booked.Duration = TimeIntervalToTimeSpan(TimeInterval); ass.Booked.Finish = ass.Booked.Start + ass.Booked.Duration; if ((AssignmentType == CalendarAssignmentType.Actual) || ((AssignmentType == CalendarAssignmentType.Automatic) && (ass.Date <= DateTime.Today))) { ass.Actual.Start = ass.Booked.Start; ass.Actual.Duration = ass.Booked.Duration; ass.Actual.Finish = ass.Actual.Start + ass.Actual.Duration; } ass.EmployeeLink.ID = slot.EmployeeID; new Client().Save(ass, ""); UpdateAssignment(ass); _copiedmodel = null; return ass; } private void Bookings_OnAppointmentTapped(object? sender, AppointmentTappedArgs e) { //if (e.Appointment is AssignmentModel model) if (e.Appointment is AssignmentAppointment appointment) { ICalendarDataEventArgs args = new CalendarDataEventArgs( appointment.Model.Row?.ToObject() ); SelectionChanged?.Invoke(this, args); } else SelectionChanged?.Invoke(this, new CalendarDataEventArgs(null)); } private void Calendar_OnSizeChanged(object sender, SizeChangedEventArgs e) { if (double.IsNaN(this.ActualHeight) || (this.ActualHeight == 0)) return; UpdateZoom(); } private void _settingsButton_OnClick(object sender, RoutedEventArgs e) { _splitPanel.View = DynamicSplitPanelView.Master; } private void _splitPanel_OnOnChanged(object sender, DynamicSplitPanelSettings e) { if (EventSuppressor.IsSet(Suppress.Events)) return; if (e.View == DynamicSplitPanelView.Combined && SettingsVisible != CalendarSettingsVisibility.Visible) SetSettingsVisibility(CalendarSettingsVisibility.Visible); else if (e.View == DynamicSplitPanelView.Master && SettingsVisible == CalendarSettingsVisibility.Visible) SetSettingsVisibility(CalendarSettingsVisibility.Hidden); } private void Bookings_OnQueryAppointments(object? sender, QueryAppointmentsEventArgs e) { if (bFirst) return; SelectedDate = e.VisibleDateRange.StartDate.Date; } private void AlwaysTodayBox_Checked(object sender, RoutedEventArgs e) { if (EventSuppressor.IsSet(Suppress.Events)) return; Properties.AlwaysStartOnToday = AlwaysTodayBox.IsChecked == true; SaveSettings?.Invoke(this, Properties); } } }