using Comal.Classes; using InABox.Clients; using InABox.Core; using InABox.DynamicGrid; using InABox.Reports; using InABox.Core.Reports; using InABox.Scripting; using InABox.WPF; using InABox.Wpf; using PRSDesktop.Configuration; using PRSDesktop.Forms; using PRSDesktop.WidgetGroups; using Syncfusion.UI.Xaml.Grid; using Syncfusion.UI.Xaml.Grid.Converter; using Syncfusion.XlsIO; using System; using System.Collections; using System.Collections.Generic; using System.Data; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using InABox.Configuration; using SelectionChangedEventArgs = System.Windows.Controls.SelectionChangedEventArgs; using InABox.Wpf.Reports; using System.ComponentModel; using Syncfusion.Windows.Shared; using System.Globalization; using System.Windows.Media.Imaging; using System.Drawing; using Image = System.Windows.Controls.Image; using Microsoft.Win32; using Syncfusion.CompoundFile.DocIO.Net; using System.IO; using Columns = InABox.Core.Columns; using PanelAction = InABox.Wpf.PanelAction; namespace PRSDesktop; public enum DateFilterType { Today, Yesterday, Week, SevenDays, Month, ThirtyDays, Year, TwelveMonths, Custom } public class DFFilter : BaseObject { [EditorSequence(1)] [TextBoxEditor] public string Name { get; set; } = ""; [EditorSequence(2)] [FilterEditor] public string Filter { get; set; } = ""; } public class DigitalFormsDashboardProperties : IUserConfigurationSettings, IDashboardProperties { public bool ShowJobFilter { get; set; } = false; public bool ShowDateFilter { get; set; } = true; public bool ShowIncompleteForms { get; set; } = false; public bool UseIconsForFormTypes { get; set; } = false; public string? Category { get; set; } public Guid SelectedForm { get; set; } public Guid JobID { get; set; } public DateFilterType DateFilterType { get; set; } = DateFilterType.Today; public DateTime FromDate { get; set; } public DateTime ToDate { get; set; } public Dictionary> Filters { get; set; } = new(); } public class DigitalFormsDashboardElement : DashboardElement { } /// /// Interaction logic for DigitalFormsDashboard.xaml /// public partial class DigitalFormsDashboard : UserControl, IDashboardWidget, IBasePanel, IRequiresSecurity, IHeaderDashboard, IActionsDashboard { public DigitalFormsDashboardProperties Properties { get; set; } public event LoadSettings? LoadSettings; public event SaveSettings? SaveSettings; private List DigitalForms; private List Jobs; private List> Categories; public DashboardHeader Header { get; set; } = new(); private bool IsQAForm = false; private List Questions = new(); private bool IsSetup = false; public DigitalFormsDashboard() { InitializeComponent(); Grid.OnGetRowStyle = Grid_OnGetRowStyle; } public void Setup() { var results = Client.QueryMultiple( new KeyedQueryDef(new Filter(x => x.Active).IsEqualTo(true)), new KeyedQueryDef( LookupFactory.DefineFilter(), Columns.None().Add(x => x.ID) .Add(x => x.JobNumber) .Add(x => x.Name))); DigitalForms = results.Get().ToObjects().ToList(); var categories = new DigitalFormCategoryLookups(null); categories.OnAfterGenerateLookups += (sender, entries) => { entries.Insert(0, new LookupEntry("", "Select Category")); }; Categories = categories.AsTable("AppliesTo") .Rows.Select(x => { var appliesTo = x.Get("AppliesTo"); if (CategoryToType(appliesTo, out var formType, out var parentType)) { return new Tuple(appliesTo, formType, x.Get("Display")); } else { return new Tuple(appliesTo, null, x.Get("Display")); } }).ToList(); Grid.Categories = Categories; Jobs = results.Get().ToObjects().ToList(); Jobs.Insert(0, new Job { ID = Guid.Empty, JobNumber = "ALL", Name = "All Jobs" }); SetupHeader(); SetupFilters(); IsSetup = true; } #region Header private StackPanel CategoryButtonPanel; private Dictionary CategoryButtons = new(); private ComboBox CategoryBox; private ComboBox FormBox; private ComboBox JobBox; private ComboBox IncompleteFormsBox; private ComboBox DateTypeBox; private Label FromLabel; private DatePicker FromPicker; private Label ToLabel; private DatePicker ToPicker; private Button Print; private Button FilterBtn; private static Dictionary FilterTypes = new() { { DateFilterType.Today, "Today" }, { DateFilterType.Yesterday, "Yesterday" }, { DateFilterType.Week, "Week to Date" }, { DateFilterType.SevenDays, "Last 7 Days" }, { DateFilterType.Month, "Month to Date" }, { DateFilterType.ThirtyDays, "Last 30 Days" }, { DateFilterType.Year, "Year to Date" }, { DateFilterType.TwelveMonths, "Last 12 Months" }, { DateFilterType.Custom, "Custom" } }; private static readonly SolidColorBrush EnabledBrush = new SolidColorBrush(Colors.LightYellow); private static readonly SolidColorBrush DisabledBrush = new SolidColorBrush(Colors.LightGray); private static readonly Dictionary CategoryImages = new() { { typeof(AssignmentForm), PRSDesktop.Resources.assignments }, { typeof(KanbanForm), PRSDesktop.Resources.kanban }, { typeof(JobForm), PRSDesktop.Resources.project }, { typeof(JobITPForm), PRSDesktop.Resources.checklist }, { typeof(EmployeeForm), PRSDesktop.Resources.employees }, { typeof(LeaveRequestForm), PRSDesktop.Resources.leave }, { typeof(ManufacturingPacketStage), PRSDesktop.Resources.factory }, { typeof(TimeSheetForm), PRSDesktop.Resources.time }, { typeof(PurchaseOrderItemForm), PRSDesktop.Resources.purchase }, { typeof(DeliveryForm), PRSDesktop.Resources.truck }, }; public void SetupHeader() { CategoryBox = new ComboBox { Width = 150, VerticalContentAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 5, 0) }; CategoryBox.ItemsSource = Categories; CategoryBox.SelectedValuePath = "Item1"; CategoryBox.DisplayMemberPath = "Item3"; CategoryBox.SelectedValue = Properties.Category; CategoryBox.SelectionChanged += Category_SelectionChanged; CategoryButtonPanel = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 0, 5, 0) }; CategoryButtons.Clear(); foreach(var (appliesTo, category, display) in Categories) { if(category is null) { continue; } var button = new Button(); button.Tag = appliesTo; button.Margin = new Thickness(0, 0, 2, 0); button.BorderBrush = new SolidColorBrush(Colors.Gray); button.BorderThickness = new Thickness(0.75); button.Width = 25D; button.Padding = new Thickness(2); button.ToolTip = category.EntityName().Split('.').Last().SplitCamelCase(); if (CategoryImages.TryGetValue(category, out var image)) { button.Content = new Image { Source = image.AsBitmapImage() }; } else { button.Content = display; } button.Click += CatagoryButton_Click; CategoryButtons.Add(category, button); CategoryButtonPanel.Children.Add(button); } if (Properties.UseIconsForFormTypes) { CategoryButtonPanel.Visibility = Visibility.Visible; CategoryBox.Visibility = Visibility.Collapsed; } else { CategoryButtonPanel.Visibility = Visibility.Collapsed; CategoryBox.Visibility = Visibility.Visible; } FormBox = new ComboBox { Width = 250, VerticalContentAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 5, 0), IsEnabled = false }; FormBox.SelectionChanged += FormBox_SelectionChanged; FormBox.ItemsSource = new Dictionary { }; JobBox = new ComboBox { Width = 250, Margin = new Thickness(0, 0, 5, 0), VerticalContentAlignment = VerticalAlignment.Center }; JobBox.ItemsSource = Jobs.ToDictionary(x => x.ID, x => $"{x.JobNumber} : {x.Name}"); JobBox.SelectedIndex = 0; JobBox.SelectedValuePath = "Key"; JobBox.DisplayMemberPath = "Value"; JobBox.SelectionChanged += JobBox_SelectionChanged; DateTypeBox = new ComboBox { Width = 120, VerticalContentAlignment = VerticalAlignment.Center }; DateTypeBox.ItemsSource = FilterTypes; DateTypeBox.SelectedValuePath = "Key"; DateTypeBox.DisplayMemberPath = "Value"; DateTypeBox.SelectedValue = Properties.DateFilterType; DateTypeBox.SelectionChanged += DateTypeBox_SelectionChanged; FromLabel = new Label { Content = "From", VerticalContentAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 5, 0) }; FromPicker = new DatePicker { Width = 100, Background = new SolidColorBrush(Colors.LightYellow), VerticalContentAlignment = VerticalAlignment.Center, FirstDayOfWeek = DayOfWeek.Monday, Margin = new Thickness(0, 0, 5, 0) }; FromPicker.SelectedDateChanged += FromPicker_SelectedDateChanged; ToLabel = new Label { Content = "To", VerticalContentAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 5, 0) }; ToPicker = new DatePicker { Width = 100, Background = new SolidColorBrush(Colors.LightYellow), VerticalContentAlignment = VerticalAlignment.Center, FirstDayOfWeek = DayOfWeek.Monday, Margin = new Thickness(0, 0, 5, 0) }; ToPicker.SelectedDateChanged += ToPicker_SelectedDateChanged; IncompleteFormsBox = new ComboBox { Width = 130, Margin = new Thickness(0, 0, 5, 0), VerticalContentAlignment = VerticalAlignment.Center }; IncompleteFormsBox.ItemsSource = new List> { new Tuple("Completed Forms", false), new Tuple("All Forms", true) }; IncompleteFormsBox.DisplayMemberPath = "Item1"; IncompleteFormsBox.SelectedValuePath = "Item2"; IncompleteFormsBox.SelectedValue = Properties.ShowIncompleteForms; IncompleteFormsBox.SelectionChanged += IncompleteFormsBox_SelectionChanged; Print = new Button { Width = 25, Height = 25, Content = new Image { Source = PRSDesktop.Resources.printer.AsBitmapImage() } }; Print.Click += Print_Click; FilterBtn = new Button { Width = 25, Height = 25, Content = new Image { Source = InABox.Wpf.Resources.filter.AsBitmapImage() }, Margin = new Thickness(0, 0, 5, 0) }; FilterBtn.Click += Filter_Click; Header.BeginUpdate() .Clear() .Add(CategoryBox) .Add(CategoryButtonPanel) .Add(FormBox) .Add(JobBox) .Add(DateTypeBox) .Add(FromLabel) .Add(FromPicker) .Add(ToLabel) .Add(ToPicker) .Add(IncompleteFormsBox) .AddRight(FilterBtn) .AddRight(Print) .EndUpdate(); UpdateCategory(Properties.Category); } private void IncompleteFormsBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { Properties.ShowIncompleteForms = (bool)IncompleteFormsBox.SelectedValue; Refresh(); } private void Filter_Click(object sender, RoutedEventArgs e) { var menu = new ContextMenu(); menu.AddCheckItem("Use Form Type Icons", ToggleFormTypeIcons, isChecked: Properties.UseIconsForFormTypes); menu.AddCheckItem("Show Date Filter", ToggleDateFilter, Properties.ShowDateFilter); menu.AddCheckItem("Show Job Filter", ToggleJobFilter, Properties.ShowJobFilter); menu.AddSeparator(); if (ParentType is not null) { if (Properties.Filters.TryGetValue(ParentType.Name, out var filters)) { var i = 0; var items = new List(); foreach (var filter in filters) { items.Add(menu.AddCheckItem( filter.Name, new Tuple>(i, filter.Filter, items), ExecuteFilter, i == CustomFilterIndex)); ++i; } } menu.AddSeparatorIfNeeded(); menu.AddItem("Manage Filters", InABox.Wpf.Resources.filter, ManageFilters_Click); } menu.IsOpen = true; } private void ToggleFormTypeIcons(bool isChecked) { Properties.UseIconsForFormTypes = !Properties.UseIconsForFormTypes; if (Properties.UseIconsForFormTypes) { CategoryButtonPanel.Visibility = Visibility.Visible; CategoryBox.Visibility = Visibility.Collapsed; } else { CategoryButtonPanel.Visibility = Visibility.Collapsed; CategoryBox.Visibility = Visibility.Visible; } } private void Print_Click(object sender, RoutedEventArgs e) { var menu = new ContextMenu(); foreach (var report in ReportUtils.LoadReports(SectionName, DataModel(Selection.None))) { menu.AddItem(report.Name, PRSDesktop.Resources.printer, report, PrintReport_Click); } if (Security.IsAllowed()) { menu.AddSeparatorIfNeeded(); menu.AddItem("Manage Reports", PRSDesktop.Resources.printer, ManageReports_Click); } menu.IsOpen = true; } private void PrintReport_Click(ReportTemplate obj) { Selection selection; if (obj.SelectedRecords && obj.AllRecords) selection = RecordSelectionDialog.Execute(); else if (obj.SelectedRecords) selection = Selection.Selected; else if (obj.AllRecords) selection = Selection.All; else selection = Selection.None; ReportUtils.PreviewReport(obj, DataModel(selection), false, Security.IsAllowed()); } private void ManageReports_Click() { var manager = new ReportManager() { DataModel = DataModel(Selection.None), Section = SectionName, Populate = true }; manager.ShowDialog(); } private void Search_KeyUp(object sender, KeyEventArgs e) { Refresh(); } private void JobBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { Properties.JobID = (Guid)JobBox.SelectedValue; Refresh(); } private void SetDateFilterVisibility(bool visible) { var visibility = visible ? Visibility.Visible : Visibility.Collapsed; FromLabel.Visibility = visibility; FromPicker.Visibility = visibility; ToLabel.Visibility = visibility; ToPicker.Visibility = visibility; DateTypeBox.Visibility = visibility; } private void SetJobFilterVisibility(bool visible) { var visibility = visible ? Visibility.Visible : Visibility.Collapsed; JobBox.Visibility = visibility; } private void DateTypeBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { var filterType = (DateFilterType)DateTypeBox.SelectedValue; Properties.DateFilterType = filterType; if (filterType == DateFilterType.Custom) { if (FromPicker.SelectedDate == null || FromPicker.SelectedDate == DateTime.MinValue) { Properties.FromDate = DateTime.Today; } else { Properties.FromDate = FromPicker.SelectedDate.Value; } if (ToPicker.SelectedDate == null || ToPicker.SelectedDate == DateTime.MinValue) { Properties.ToDate = DateTime.Today; } else { Properties.ToDate = ToPicker.SelectedDate.Value; } } SetupDateFilters(); Refresh(); } private void FromPicker_SelectedDateChanged(object? sender, SelectionChangedEventArgs e) { Properties.FromDate = FromPicker.SelectedDate ?? DateTime.Today; Refresh(); } private void ToPicker_SelectedDateChanged(object? sender, SelectionChangedEventArgs e) { Properties.ToDate = ToPicker.SelectedDate ?? DateTime.Today; Refresh(); } private bool _changing = false; private void UpdateCategory(string? category) { _changing = true; Properties.Category = category; SetCategory(Properties.Category); foreach(var (type, button) in CategoryButtons) { if(type == FormType) { button.Background = EnabledBrush; } else { button.Background = DisabledBrush; } } var jobLink = FormType is not null ? GetJobLink("", FormType,true) : ""; if (string.IsNullOrWhiteSpace(jobLink)) { var jobID = Properties.JobID; JobBox.SelectedValue = Guid.Empty; JobBox.IsEnabled = false; Properties.JobID = jobID; } else { JobBox.SelectedValue = Properties.JobID; JobBox.IsEnabled = true; } string changeableJobLink = ""; string changeableScopeLink = ""; if ((FormType != null) && ChangeableLinks.TryGetValue(FormType, out Tuple _tuple)) { changeableJobLink = _tuple.Item1; changeableScopeLink = _tuple.Item2; } if (_jobAction != null) _jobAction.Visibility = string.IsNullOrWhiteSpace(changeableJobLink) ? Visibility.Collapsed : Visibility.Visible; if (_jobScopeAction != null) _jobScopeAction.Visibility = string.IsNullOrWhiteSpace(changeableScopeLink) ? Visibility.Collapsed : Visibility.Visible; if (ParentType is null) { FormBox.IsEnabled = false; FormBox.ItemsSource = new Dictionary { }; } else { var forms = DigitalForms.Where(x => x.AppliesTo == ParentType.Name).ToList(); forms.Insert(0, new DigitalForm { ID = Guid.Empty, Description = "Select Form" }); FormBox.ItemsSource = forms; if (Properties.SelectedForm != Guid.Empty && forms.Where(x => x.ID == Properties.SelectedForm).FirstOrDefault() is DigitalForm form) { FormBox.SelectedItem = form; } else { FormBox.SelectedIndex = 0; } FormBox.DisplayMemberPath = "Description"; FormBox.IsEnabled = true; } _changing = false; OnUpdateDataModel?.Invoke(SectionName, DataModel(Selection.None)); } private void CatagoryButton_Click(object sender, RoutedEventArgs e) { UpdateCategory(((sender as Button)!.Tag as string)!); Refresh(); } private void Category_SelectionChanged(object sender, SelectionChangedEventArgs e) { UpdateCategory((CategoryBox.SelectedValue as string)!); Refresh(); } private void FormBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { Form = FormBox.SelectedValue as DigitalForm; Properties.SelectedForm = Form?.ID ?? Guid.Empty; if (!_changing) { OnUpdateDataModel?.Invoke(SectionName, DataModel(Selection.None)); } Refresh(); } #endregion #region IBasePanel public bool IsReady { get; set; } public event DataModelUpdateEvent? OnUpdateDataModel; private PanelAction? _jobAction = null; private PanelAction? _jobScopeAction = null; public void CreateToolbarButtons(IPanelHost host) { host.CreatePanelAction(new PanelAction("Export Forms", PRSDesktop.Resources.disk, action => SaveToFolder_Click())); _jobAction ??= new PanelAction("Set Job Number", PRSDesktop.Resources.project, SetJobNumber); host.CreatePanelAction(_jobAction); _jobScopeAction ??= new PanelAction( "Set Job Scope", PRSDesktop.Resources.project, null, LoadJobScopes ); host.CreatePanelAction(_jobScopeAction); } private IPanelActionEntry[] LoadJobScopes(PanelAction arg) { List result = new(); var linkcolumn = GetJobLink("", FormType, true); if (!string.IsNullOrWhiteSpace(linkcolumn)) { linkcolumn = $"{linkcolumn}.ID"; var jobid = Grid.SelectedRows.Select(x => x[linkcolumn]).FirstOrDefault(); if (jobid != null && !Guid.Equals(jobid, Guid.Empty)) { var scopes =Client.Query( new Filter(x => x.Job.ID).IsEqualTo((Guid)jobid), Columns.None() .Add(x => x.ID) .Add(x => x.Number) .Add(x => x.Description) ).ToObjects(); foreach (var scope in scopes) { var entry = new PanelActionEntry($"{scope.Number}: {scope.Description}", PRSDesktop.Resources.project, scope.ID, SetJobScope); result.Add(entry); } } } return result.ToArray(); } private void SetJobScope(PanelActionEntry scopeid) { Type? _type = FormType == typeof(JobForm) ? FormType : ParentType; if (_type == null) return; string _idCol = FormType == typeof(JobForm) ? "ID" : "Parent.ID"; var _ids = Grid.SelectedRows.Select(x => x[_idCol]).OfType().Distinct().ToArray(); Progress.ShowModal("Updating Job Scopes", progress => { var _updates = ClientFactory.CreateClient(_type) .Query( Filter.Create(_type, "ID", Operator.InList, _ids), Columns.Create(_type, ColumnTypeFlags.Required) ).ToObjects(_type).ToArray(); var _scopelink = GetJobScopeLink("", _type, FormType == typeof(JobForm) ? 0 : 1); foreach (var _update in _updates) CoreUtils.SetPropertyValue(_update, _scopelink + ".ID", scopeid.Data); ClientFactory.CreateClient(_type).Save(_updates, "Updated Job Number"); }); Refresh(); } private void SetJobNumber(PanelAction obj) { var dlg = new MultiSelectDialog( LookupFactory.DefineFilter(), Columns.None() .Add(x => x.ID) .Add(x => x.JobNumber) .Add(x => x.Name) , false ); if (dlg.ShowDialog()) { Guid _jobID = dlg.IDs().FirstOrDefault(); Type? _type = FormType == typeof(JobForm) ? FormType : ParentType; if (_type == null) return; string _idCol = FormType == typeof(JobForm) ? "ID" : "Parent.ID"; var _ids = Grid.SelectedRows.Select(x => x[_idCol]).OfType().Distinct().ToArray(); Progress.ShowModal("Updating Job Numbers", progress => { var _updates = ClientFactory.CreateClient(_type) .Query( Filter.Create(_type, "ID", Operator.InList, _ids), Columns.Create(_type, ColumnTypeFlags.Required) ).ToObjects(_type).ToArray(); var _joblink = GetJobLink("", _type, false); foreach (var _update in _updates) CoreUtils.SetPropertyValue(_update, _joblink + ".ID", _jobID); ClientFactory.CreateClient(_type).Save(_updates, "Updated Job Number"); }); Refresh(); } } public void Heartbeat(TimeSpan time) { } public Dictionary Selected() { return new Dictionary(); } #endregion public string SectionName { get { if (Form is null || Form.ID == Guid.Empty) return "Digital Forms"; return Form.ID.ToString() ?? "Digital Forms"; } } public DataModel DataModel(Selection selection) { if (FormType is null || Form is null || Form.ID == Guid.Empty) { return new AutoDataModel(new Filter().None()); } IFilter filter; switch (selection) { case Selection.Selected: var formids = Grid.SelectedRows.ToArray(x => x.Get("ID")); filter = Filter.Create(FormType, x => x.ID).InList(formids); break; case Selection.All: filter = Filter.Create(FormType).All(); break; case Selection.None: default: filter = Filter.Create(FormType).None(); break; } return (Activator.CreateInstance(typeof(DigitalFormReportDataModel<>)! .MakeGenericType(FormType), new object?[] { filter, Form.ID }) as DataModel)!; } public void BuildActionsMenu(ContextMenu menu) { menu.AddItem("Export", InABox.Wpf.Resources.doc_xls, Export_Click, Form is not null && Form.ID != Guid.Empty); var loadingModules = menu.AddItem("Loading Modules...", null, null, false); Task.Run(() => { return CustomModuleUtils.LoadCustomModuleThumbnails(SectionName, DataModel(Selection.None)); }).ContinueWith((task) => { var modules = task.Result; var index = menu.Items.IndexOf(loadingModules); menu.Items.RemoveAt(index); foreach (var (module, image) in modules) { menu.AddItem(module.Name, image, module, ExecuteModule_Click, index: index); ++index; } }, TaskScheduler.FromCurrentSynchronizationContext()); if (Security.IsAllowed()) { menu.AddSeparatorIfNeeded(); menu.AddItem("Manage Modules", PRSDesktop.Resources.script, ManageModules_Click); } } private void SaveToFolder_Click() { if (Form is null || FormType is null) { MessageWindow.ShowMessage("Please select a form first.", "Select form"); return; } var model = DataModel(Selection.None); var reports = ReportUtils.LoadReports(Form.ID.ToString(), model).Where(x => x.Visible).ToList(); var method = typeof(DigitalFormsDashboard).GetMethod("SaveToFolder", BindingFlags.Instance | BindingFlags.NonPublic)!.MakeGenericMethod(FormType); if (reports.Count == 0) { MessageWindow.ShowMessage("No reports are currently defined for this Digital Form!", "No report found"); return; } if(reports.Count == 1) { method.Invoke(this, new object[] { reports[0] }); return; } var menu = new ContextMenu(); foreach (var report in reports) menu.AddItem(report.Name, null, report, r => method.Invoke(this, new[] { r })); menu.IsOpen = true; } private void SaveToFolder(ReportTemplate report) where TForm : Entity, IDigitalFormInstance, IRemotable, IPersistent, new() { using (var dialog = new System.Windows.Forms.FolderBrowserDialog()) { var result = dialog.ShowDialog(); if(result == System.Windows.Forms.DialogResult.OK && !string.IsNullOrWhiteSpace(dialog.SelectedPath)) { var data = Grid.SelectedRows; Progress.ShowModal("Saving forms", progress => { foreach (var row in data) { var id = row.Get("ID"); var number = row.Get("Number"); progress.Report($"Saving form {number}"); var dataModel = new DigitalFormReportDataModel( new Filter(x => x.ID).IsEqualTo(id), Form!.ID); var pdfData = ReportUtils.ReportToPDF(report, dataModel, true); var expr = dataModel.EvaluateExpression(Form.ExportExpression)?.Trim(); var filename = String.IsNullOrWhiteSpace(expr) ? number : $"{number} - {CoreUtils.SanitiseFileName(expr)}"; File.WriteAllBytes(Path.Combine(dialog.SelectedPath, Path.ChangeExtension(filename, ".pdf")), pdfData); } }); Process.Start("explorer.exe" , dialog.SelectedPath); } } } private void Export_Click() { // var formName = Regex.Replace(Form?.Description ?? "", "[^ a-zA-Z0-9]", ""); // var filename = string.Format("{0} - {1} - {2:yyyy-MM-dd} - {3:yyyy-MM-dd}.xlsx", ParentType!.Name, formName, From, To); // var options = new ExcelExportingOptions(); // options.ExcelVersion = ExcelVersion.Excel2013; // options.ExportStackedHeaders = true; // var excelEngine = DataGrid.ExportToExcel(DataGrid.View, options); // var workBook = excelEngine.Excel.Workbooks[0]; // var sheet = workBook.Worksheets[0]; // sheet.Name = "Summary"; // sheet.UsedRange.AutofitRows(); // sheet.UsedRange.AutofitColumns(); // sheet = workBook.Worksheets.Create("Questions"); // sheet.Move(0); // var questions = new Client().Query(new Filter(x => x.Form.ID).IsEqualTo(Form!.ID)); // sheet.Range[1, 1].Text = Form?.Description ?? ""; // sheet.Range[1, 1, 1, 3].Merge(); // var i = 1; // foreach (var row in questions.Rows) // if (!row.Get(x => x.Answer).Equals(QAAnswer.Comment)) // { // sheet.Range[i + 2, 1].Text = string.Format("{0}.", i); // sheet.Range[i + 2, 2].Text = string.Format("{0}", row.Get(x => x.Question)); // sheet.Range[i + 2, 3].Text = string.Format("[{0}]", row.Get(x => x.Code)); // i++; // } // sheet.UsedRange.AutofitRows(); // sheet.UsedRange.AutofitColumns(); // try // { // workBook.SaveAs(filename); // var startInfo = new ProcessStartInfo(filename); // startInfo.Verb = "open"; // startInfo.UseShellExecute = true; // Process.Start(startInfo); // } // catch // { // MessageBox.Show(string.Format("Unable to Save/Launch [{0}]!\n\nIs the file already open?", filename)); // } } private void ManageFilters_Click() { var filters = Properties.Filters.GetValueOrDefault(ParentType!.Name) ?? new List(); var gridFilters = new CoreFilterDefinitions(); gridFilters.AddRange(filters.Select(x => new CoreFilterDefinition { Name = x.Name, Filter = x.Filter })); var grid = new DynamicGridFilterEditor(gridFilters, FormType!); if (grid.ShowDialog() == true) { Properties.Filters[ParentType!.Name] = grid.Filters.Select(x => new DFFilter { Name = x.Name, Filter = x.Filter }).ToList(); if (CustomFilterIndex != null) { Refresh(); } } } private void ExecuteFilter(Tuple> tag, bool isChecked) { var (index, filter, items) = tag; if (isChecked) { var i = 0; foreach (var item in items) { item.IsChecked = i == index; ++i; } } if (isChecked) { CustomFilter = Serialization.Deserialize(typeof(Filter<>).MakeGenericType(FormType!), filter) as IFilter; CustomFilterIndex = index; Refresh(); } else if (index == CustomFilterIndex) { CustomFilter = null; CustomFilterIndex = null; Refresh(); } } private void ExecuteModule_Click(CustomModule obj) { if (!string.IsNullOrWhiteSpace(obj.Script)) try { Selection selection; if (obj.SelectedRecords && obj.AllRecords) selection = RecordSelectionDialog.Execute(); else if (obj.SelectedRecords) selection = Selection.Selected; else if (obj.AllRecords) selection = Selection.All; else selection = Selection.None; var result = ScriptDocument.RunCustomModule(DataModel(selection), new Dictionary(), obj.Script); if (result) Refresh(); } catch (CompileException c) { MessageBox.Show(c.Message); } catch (Exception err) { MessageBox.Show(CoreUtils.FormatException(err)); } else MessageBox.Show("Unable to load " + obj.Name); } private void ManageModules_Click() { var section = SectionName; var dataModel = DataModel(Selection.Selected); var manager = new CustomModuleManager() { Section = section, DataModel = dataModel }; manager.ShowDialog(); } private void ToggleDateFilter(bool isChecked) { Properties.ShowDateFilter = isChecked; SetDateFilterVisibility(Properties.ShowDateFilter); } private void ToggleJobFilter(bool isChecked) { Properties.ShowJobFilter = isChecked; SetJobFilterVisibility(Properties.ShowJobFilter); Refresh(); } #region Filtering private DateTime From { get; set; } private DateTime To { get; set; } private bool IsEntityForm { get; set; } private Type? _parentType; private Type? ParentType { get => _parentType; set { _parentType = value; Grid.ParentType = value; } } private Type? _formType; private Type? FormType { get => _formType; set { _formType = value; Grid.FormType = value; } } private DigitalForm? Form { get; set; } private IFilter? CustomFilter { get; set; } private int? CustomFilterIndex { get; set; } private static int WeekDay(DateTime date) { if (date.DayOfWeek == DayOfWeek.Sunday) return 7; return (int)date.DayOfWeek - 1; } private void SetupDateFilters() { switch (Properties.DateFilterType) { case DateFilterType.Today: From = DateTime.Today; To = DateTime.Today; break; case DateFilterType.Yesterday: From = DateTime.Today.AddDays(-1); To = DateTime.Today.AddDays(-1); break; case DateFilterType.Week: From = DateTime.Today.AddDays(-WeekDay(DateTime.Today)); To = DateTime.Today; break; case DateFilterType.SevenDays: From = DateTime.Today.AddDays(-6); To = DateTime.Today; break; case DateFilterType.Month: From = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1); To = DateTime.Today; break; case DateFilterType.ThirtyDays: From = DateTime.Today.AddDays(-29); To = DateTime.Today; break; case DateFilterType.Year: From = new DateTime(DateTime.Today.Year, 1, 1); To = DateTime.Today; break; case DateFilterType.TwelveMonths: From = DateTime.Today.AddYears(-1).AddDays(1); To = DateTime.Today; break; case DateFilterType.Custom: From = Properties.FromDate; To = Properties.ToDate; break; } DateTypeBox.SelectedValue = Properties.DateFilterType; FromPicker.SelectedDate = From; ToPicker.SelectedDate = To; var enabledPicker = Properties.DateFilterType == DateFilterType.Custom; FromPicker.IsEnabled = enabledPicker; ToPicker.IsEnabled = enabledPicker; } private void SetupJobFilter() { JobBox.SelectedValue = Properties.JobID; } private void SetupFilters() { SetupDateFilters(); SetupJobFilter(); SetDateFilterVisibility(Properties.ShowDateFilter); SetJobFilterVisibility(Properties.ShowJobFilter); } #region Categories private static Dictionary>? FormInstanceTypes; public static readonly Dictionary>> ParentColumns = new() { { typeof(Kanban), new() { new("Parent.Number", "Task No") } }, { typeof(JobITP), new() { new("Parent.Code", "Code") } }, { typeof(Assignment), new() { new("Parent.Number", "Ass. No") } }, { typeof(Employee), new() { new("Parent.Code", "Employee") } }, { typeof(PurchaseOrderItem), new() { new("Parent.PONumber", "PO No") } } }; public static Dictionary> ChangeableLinks = new() { { typeof(JobForm), new( "Parent.ID", "Scope.ID") }, { typeof(AssignmentForm), new( "JobLink.ID", "JobScope.ID") }, { typeof(KanbanForm), new( "JobLink.ID", "") } }; private static bool CategoryToType(string category, [NotNullWhen(true)] out Type? formType, [NotNullWhen(true)] out Type? parentType) { if (FormInstanceTypes == null) { var instancetypes = CoreUtils.Entities.Where( x => x.GetInterfaces().Contains(typeof(IDigitalFormInstance)) ).Select(x => { var inter = x.GetInterfaces() .Where(x => x.IsGenericType && x.GetGenericTypeDefinition().Equals(typeof(IDigitalFormInstance<>))) .FirstOrDefault(); if (inter is not null) { var link = inter.GenericTypeArguments[0]; var entityLinkDef = link.GetSuperclassDefinition(typeof(EntityLink<>)); if (entityLinkDef is not null) { var entityType = entityLinkDef.GenericTypeArguments[0]; return new Tuple(entityType.Name, x, entityType); } } return null; }).Where(x => x is not null); FormInstanceTypes = instancetypes.ToDictionary(x => x!.Item1, x => new Tuple(x!.Item2, x!.Item3)); } if (!FormInstanceTypes.TryGetValue(category, out var result)) { formType = null; parentType = null; return false; } formType = result.Item1; parentType = result.Item2; return true; } private void SetCategory(string? category) { CustomFilter = null; CustomFilterIndex = null; if (category is null || !CategoryToType(category, out var formType, out var parentType)) { IsEntityForm = false; ParentType = null; FormType = null; return; } IsEntityForm = formType.IsSubclassOfRawGeneric(typeof(EntityForm<,,>)); ParentType = parentType; FormType = formType; } #endregion private static string GetJobLink(string prefix, Type type, bool recursive) { if (type == null) return "null"; var props = type.GetProperties().Where(x => x.PropertyType.BaseType != null && x.PropertyType.BaseType.IsGenericType && x.PropertyType.BaseType.GetGenericTypeDefinition() == typeof(EntityLink<>)); foreach (var prop in props) { if (prop.PropertyType == typeof(JobLink)) return (string.IsNullOrEmpty(prefix) ? "" : prefix + ".") + prop.Name; if (recursive) { var result = GetJobLink((string.IsNullOrEmpty(prefix) ? "" : prefix + ".") + prop.Name, prop.PropertyType, recursive); if (!string.IsNullOrEmpty(result)) return result; } } return ""; } private static string GetJobScopeLink(string prefix, Type type, int recurselevel) { var props = type.GetProperties().Where(x => x.PropertyType.BaseType != null && x.PropertyType.BaseType.IsGenericType && x.PropertyType.BaseType.GetGenericTypeDefinition() == typeof(EntityLink<>)); foreach (var prop in props) { if (prop.PropertyType == typeof(JobScopeLink)) return (string.IsNullOrEmpty(prefix) ? "" : prefix + ".") + prop.Name; if (recurselevel > 0) { var result = GetJobScopeLink((string.IsNullOrEmpty(prefix) ? "" : prefix + ".") + prop.Name, prop.PropertyType, recurselevel - 1); if (!string.IsNullOrEmpty(result)) return result; } } return ""; } /// /// Find a link from the form type to an associated , allowing us to filter based on jobs. /// /// The property name of the . private static string GetJobLink(bool recursive) where T : IDigitalFormInstance => GetJobLink("", typeof(T), recursive); private static string GetJobScopeLink(int recurselevel) where T : IDigitalFormInstance => GetJobScopeLink("", typeof(T), recurselevel); private IKeyedQueryDef GetFormQuery() where T : Entity, IRemotable, IPersistent, IDigitalFormInstance, new() { var sort = LookupFactory.DefineSort(); var jobLink = GetJobLink(true); var jobScopeLink = GetJobScopeLink(typeof(T) == typeof(JobForm) ? 0 : 1); var completedFilter = new Filter(x => x.FormCompleted).IsGreaterThanOrEqualTo(From) .And(x => x.FormCompleted).IsLessThan(To.AddDays(1)); if (Properties.ShowIncompleteForms) { completedFilter.Or(x => x.FormCompleted).IsEqualTo(FilterConstant.Null); } var filter = new Filter(x => x.Form.ID).IsEqualTo(Form!.ID) .And(x => x.FormCancelled).IsEqualTo(DateTime.MinValue) .And(completedFilter); if (Properties.JobID != Guid.Empty && Properties.ShowJobFilter) { filter.And(jobLink + ".ID").IsEqualTo(Properties.JobID); } if (CustomFilter is not null) { filter.And(CustomFilter); } var columns = Columns.None().Add(x => x.ID) .Add(x => x.Number) .Add(x => x.Description) .Add(x => x.CreatedBy) .Add(x => x.Created) .Add(x => x.Form.ID) .Add(x => x.FormData) .Add(x => x.FormStarted) .Add(x => x.FormCompleted) .Add(x => x.FormCompletedBy.UserID) .Add(x => x.Location.Timestamp) .Add(x => x.Location.Latitude) .Add(x => x.Location.Longitude); if (IsEntityForm) columns.Add(x => x.FormProcessed); //"Processed"); var parentcols = LookupFactory.DefineColumns(ParentType!); foreach (var col in parentcols.ColumnNames()) columns.Add("Parent." + col); if (ParentColumns.TryGetValue(ParentType!, out var pColumns)) { foreach (var (field, name) in pColumns) columns.Add(field); } if (!string.IsNullOrWhiteSpace(jobLink)) { columns.Add(jobLink + ".ID"); columns.Add(jobLink + ".JobNumber"); } if (!string.IsNullOrWhiteSpace(jobScopeLink)) { columns.Add(jobScopeLink + ".ID"); columns.Add(jobScopeLink + ".Number"); } return new KeyedQueryDef(filter, columns, sort); } #endregion private void LoadDataIntoGrid(List variables, List questions, CoreTable formData, List additionalColumns, CoreTable? jobITPs) { var cData = new CoreTable(); cData.AddColumn("ID"); cData.AddColumn("Form.ID"); cData.AddColumn("Parent.ID"); cData.AddColumn("Location.Timestamp"); cData.AddColumn("Location.Latitude"); cData.AddColumn("Location.Longitude"); cData.AddColumn("FormData"); cData.AddColumn("Number"); if (ParentColumns.TryGetValue(ParentType!, out var pColumns)) { foreach (var (field, name) in pColumns) cData.AddColumn(field); } var jobLink = GetJobLink("",FormType!,true); Grid.JobLink = jobLink; if (!string.IsNullOrWhiteSpace(jobLink)) { if (!string.Equals(jobLink, "Parent")) cData.AddColumn(jobLink + ".ID"); cData.AddColumn(jobLink + ".JobNumber"); } var jobScopeLink = GetJobScopeLink("",FormType!,FormType == typeof(JobForm) ? 0 : 1); Grid.JobScopeLink = jobLink; if (!string.IsNullOrWhiteSpace(jobScopeLink)) { cData.AddColumn(jobScopeLink + ".ID"); cData.AddColumn(jobScopeLink + ".Number"); } cData.AddColumn("Description"); cData.AddColumn("Parent.Description"); cData.AddColumn("Created"); cData.AddColumn("Created By"); cData.AddColumn("Completed"); cData.AddColumn("Completed By"); if (IsEntityForm) cData.AddColumn("Processed"); if (variables.Count != 0) { foreach (var variable in variables) { var code = variable.Code.Replace("/", " "); Grid.QuestionCodes[code] = Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase(code.ToLower()); try { cData.AddColumn(code); } catch (DuplicateNameException e) { MessageWindow.ShowError($"Error: duplicate variable code {code}", e, title: "Duplicate code"); } } } else if (questions.Count != 0) { Questions = questions; Progress.SetMessage("Loading Checks"); QAGrid.Clear(); QAGrid.LoadChecks(Form!.Description, Questions, new Dictionary()); QAGrid.CollapseMargins(); var i = 1; foreach (var question in Questions) { var id = question.ID.ToString(); if (!question.Answer.Equals(QAAnswer.Comment)) { cData.AddColumn(id); var code = question.Code; Grid.QuestionCodes[id] = string.IsNullOrEmpty(code) ? string.Format("{0}.", i) : code; i++; } } } foreach (var row in formData.Rows) { var form = (row.ToObject(FormType!) as IDigitalFormInstance)!; if (true) //(!string.IsNullOrWhiteSpace(form.FormData)) { var dataRow = cData.NewRow(); dataRow["ID"] = form.ID; dataRow["Form.ID"] = form.Form.ID; dataRow["Parent.ID"] = form.ParentID(); dataRow["Location.Timestamp"] = form.Location.Timestamp; dataRow["Location.Latitude"] = form.Location.Latitude; dataRow["Location.Longitude"] = form.Location.Longitude; dataRow["FormData"] = form.FormData; dataRow["Number"] = form.Number; dataRow["Description"] = form.Description; var desc = new List(); foreach (var col in additionalColumns) { var val = row[col]; if (val != null && val is not Guid) desc.Add(val.ToString() ?? ""); } dataRow["Parent.Description"] = string.Join(" : ", desc); dataRow["Created"] = (form as Entity)!.Created.IsEmpty() ? form.FormStarted : (form as Entity)!.Created; dataRow["Created By"] = (form as Entity)!.CreatedBy; dataRow["Completed"] = form.FormCompleted; dataRow["Completed By"] = form.FormCompletedBy.UserID; if (IsEntityForm) dataRow["Processed"] = form.FormProcessed > DateTime.MinValue; // if (ParentType == typeof(JobITP)) // { // var jobITP = jobITPs!.Rows.FirstOrDefault(x => x.Get(x => x.ID) == form.ParentID()); // if (jobITP is not null) // { // var jobID = jobITP.Get(x => x.Job.ID); // dataRow["Job No"] = Jobs.Where(x => x.ID == jobID).FirstOrDefault()?.JobNumber; // } // } if (pColumns != null) { foreach (var (field, name) in pColumns) dataRow[field] = $"{row[field]}"; } if (!string.IsNullOrWhiteSpace(jobLink)) { dataRow[jobLink + ".ID"] = row[jobLink + ".ID"]; dataRow[jobLink + ".JobNumber"] = row[jobLink + ".JobNumber"]?.ToString(); } if (!string.IsNullOrWhiteSpace(jobScopeLink)) { dataRow[jobScopeLink + ".ID"] = row[jobScopeLink + ".ID"]; dataRow[jobScopeLink + ".Number"] = row[jobScopeLink + ".Number"]?.ToString(); } var bHasData = false; if (variables.Count != 0) { var dict = Serialization.Deserialize>(form.FormData); if (dict is not null) { var storage = new DFLoadStorage(dict, null); foreach (var variable in variables) { var value = variable.Deserialize(storage.GetEntry(variable.Code)); var format = variable.FormatValue(value); var sKey = variable.Code.Replace("/", " "); if (cData.HasColumn(sKey)) { dataRow[sKey] = format; bHasData = true; } } } } else { var dict = Serialization.Deserialize>(form.FormData); if (dict is not null) foreach (var key in dict.Keys) { var colName = key.ToString(); if (cData.HasColumn(colName)) { dataRow[colName] = dict[key]; bHasData = true; } } } cData.Rows.Add(dataRow); } } Grid.ItemsSource = cData; IsQAForm = variables.Count == 0 && questions.Count != 0; QAGrid.Visibility = IsQAForm ? Visibility.Visible : Visibility.Collapsed; Grid.Visibility = Visibility.Visible; } private void RefreshData() where TForm : Entity, IRemotable, IPersistent, IDigitalFormInstance, new() { var formQuery = GetFormQuery(); var queries = new List() { new KeyedQueryDef(new Filter(x => x.Form.ID).IsEqualTo(Form!.ID)), new KeyedQueryDef( new Filter(x => x.Form.ID).IsEqualTo(Form.ID), null, new SortOrder(x => x.Sequence)), formQuery }; if (ParentType == typeof(JobITP)) { queries.Add(new KeyedQueryDef( new Filter(x => x.ID).InQuery((formQuery.Filter as Filter)!, x => x.Parent.ID), Columns.None().Add(x => x.ID, x => x.Job.JobNumber))); } var results = Client.QueryMultiple(queries); var questions = results.Get().ToObjects().ToList(); var variables = results.Get().ToObjects().ToList(); var formData = results.Get(formQuery.Key); LoadDataIntoGrid( variables, questions, formData, formQuery.Columns!.ColumnNames().Where(x => x.StartsWith("Parent.")).ToList(), ParentType == typeof(JobITP) ? results.Get() : null); } public void Refresh() { if (!IsSetup) return; Questions.Clear(); QAGrid.Clear(); QAGrid.LoadChecks("", Array.Empty(), new Dictionary()); if (ParentType is null || FormType is null || Form is null || Form.ID == Guid.Empty) { QAGrid.Visibility = Visibility.Collapsed; Grid.Visibility = Visibility.Collapsed; return; } var refreshMethod = typeof(DigitalFormsDashboard).GetMethod(nameof(RefreshData), BindingFlags.Instance | BindingFlags.NonPublic)!.MakeGenericMethod(FormType); refreshMethod.Invoke(this, Array.Empty()); } public void Shutdown(CancelEventArgs? cancel) { } #region DataGrid Configuration private DynamicGridStyle Grid_OnGetRowStyle(CoreRow row, DynamicGridStyle defaultstyle) { if (row.Get("Completed") == DateTime.MinValue) { return new DynamicGridRowStyle(defaultstyle) { Background = Colors.LightSalmon.ToBrush() }; } else { return defaultstyle; } } private Entity? GetEntityForm(Guid id) where T : Entity, IDigitalFormInstance, IRemotable, IPersistent, new() { var columns = DynamicFormEditWindow.FormColumns(); return new Client().Query( new Filter(x => x.ID).IsEqualTo(id), columns).Rows.FirstOrDefault()?.ToObject(); } #endregion private void Grid_OnSelectItem(object sender, DynamicGridSelectionEventArgs e) { var linkcolumn = GetJobLink("", FormType, true); if (!string.IsNullOrWhiteSpace(linkcolumn) && _jobScopeAction != null) { linkcolumn = $"{linkcolumn}.ID"; var jobids = (e.Rows ?? []).Select(x => x[linkcolumn]).Distinct().ToArray(); _jobScopeAction.IsEnabled = jobids.Length == 1; } } private void Grid_OnCellDoubleClick(object sender, DynamicGridCellClickEventArgs args) { if (args.Row is null) return; var formid = args.Row.Get("Form.ID"); var formdata = args.Row.Get("FormData"); var id = args.Row.Get("ID"); if (FormType is null) return; if (IsQAForm) { var values = new Dictionary(); var formData = Serialization.Deserialize>(formdata); if (formData is not null) { foreach (var (idStr, value) in formData) { if (Guid.TryParse(idStr, out var codeID)) { values[codeID] = value; } } } QAGrid.Clear(); QAGrid.LoadChecks(Form!.Description, Questions, values); QAGrid.CollapseMargins(); return; } var entityForm = typeof(DigitalFormsDashboard) .GetMethod(nameof(GetEntityForm), BindingFlags.NonPublic | BindingFlags.Instance)! .MakeGenericMethod(FormType) .Invoke(this, new object[] { id }) as IDigitalFormInstance; if (entityForm is not null) { if (DynamicFormEditWindow.EditDigitalForm(entityForm, out var dataModel)) { dataModel.Update(null); /*typeof(QADashboard) .GetMethod(nameof(SaveEntityForm), System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)! .MakeGenericMethod(formType) .Invoke(this, new object[] { entityForm });*/ Refresh(); } } } } public class DigitalFormsDashboardGrid : CoreTableGrid { public string? JobLink { get; set; } public string? JobScopeLink { get; set; } public Type? ParentType { get; set; } public Type? FormType { get; set; } public List> Categories { get; set; } = new(); public readonly Dictionary QuestionCodes = new(); protected override void Init() { base.Init(); ActionColumns.Add(new DynamicImageColumn(Location_Image, Location_Click) { Position = DynamicActionColumnPosition.Start }); } protected override void DoReconfigure(DynamicGridOptions options) { base.DoReconfigure(options); options.FilterRows = true; } private bool Location_Click(CoreRow? row) { if (row is null) return false; var timestamp = row.Get("Location.Timestamp"); if (timestamp == DateTime.MinValue) return false; var latitude = row.Get("Location.Latitude"); var longitude = row.Get("Location.Longitude"); var form = new MapForm(latitude, longitude, timestamp); form.ShowDialog(); return false; } private BitmapImage? Location_Image(CoreRow? row) { return row?.Get("Location.Timestamp") == DateTime.MinValue ? null : PRSDesktop.Resources.milestone.AsBitmapImage(); } public override DynamicGridColumns GenerateColumns() { var columns = base.GenerateColumns(); var newCols = new DynamicGridColumns(); foreach(var column in columns) { column.Alignment = Alignment.MiddleCenter; if (column.ColumnName == "ID" || column.ColumnName == "Form.ID" || column.ColumnName == "Parent.ID" || column.ColumnName == "FormData" || column.ColumnName == "Location.Latitude" || column.ColumnName == "Location.Longitude") { continue; } else if (column.ColumnName == "Number") { column.Width = 80; column.Alignment = Alignment.MiddleCenter; } else if (column.ColumnName == "Description") { column.Width = 250; column.Alignment = Alignment.MiddleLeft; } else if (column.ColumnName == "Location.Timestamp") { continue; } else if (ParentType is not null && DigitalFormsDashboard.ParentColumns.TryGetValue(ParentType, out var pColumns) && pColumns.FirstOrDefault(x => x.Item1 == column.ColumnName)?.Item2 is string header) { column.Caption = header; } else if (column.ColumnName == JobLink + ".JobNumber") { column.Width = 60; column.Caption = "Job No"; } else if (column.ColumnName == JobScopeLink + ".Number") { column.Width = 50; column.Caption = "Scope"; } else if (column.ColumnName == "Parent.Description") { column.Caption = Categories.FirstOrDefault(x => x.Item2 == FormType)?.Item3 ?? "Parent"; column.Alignment = Alignment.MiddleLeft; column.Width = 250; } else if (column.ColumnName == "Completed") { column.Width = 100; column.Format = "dd MMM yy hh:mm"; } else if (column.ColumnName == "Completed By") { column.Width = 100; } else if (column.ColumnName == "Processed") { column.Width = 100; } else if (column.ColumnName == "Created By") { column.Width = 100; } else if (column.ColumnName == "Created") { column.Width = 100; column.Format = "dd MMM yy hh:mm"; } else if (QuestionCodes.TryGetValue(column.ColumnName, out var questionHeader)) { column.Width = 100; column.Caption = questionHeader; } else { continue; } newCols.Add(column); } return newCols; } }