using Comal.Classes; using InABox.Core; using InABox.DynamicGrid; using InABox.WPF; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows.Controls; using System.Windows.Media; using InABox.Wpf; namespace PRSDesktop.Forms.Issues; public class IssuesGrid : DynamicGrid, ISpecificGrid { private readonly int ChunkSize = 500; public IQueryProviderFactory ClientFactory { get; set; } private IQueryProvider? _kanbanClient; private IQueryProvider KanbanClient { get { _kanbanClient ??= ClientFactory.Create(); return _kanbanClient; } } private IQueryProvider? _jobClient; private IQueryProvider JobClient { get { _jobClient ??= ClientFactory.Create(); return _jobClient; } } public Guid CustomerID { get; set; } // public static CustomProperty CustomerProperty = new CustomProperty // { // Name = "CustomerID", // PropertyType = typeof(string), // ClassType = typeof(Kanban) // }; public IssuesGrid() : base() { var cols = LookupFactory.DefineColumns(); // Minimum Columns for Lookup values foreach (var col in cols) HiddenColumns.Add(col); HiddenColumns.Add(x => x.Notes); ActionColumns.Add(new DynamicMenuColumn(BuildMenu) { Position = DynamicActionColumnPosition.End }); } private class UIComponent : DynamicGridGridUIComponent { private IssuesGrid Grid; public UIComponent(IssuesGrid grid) { Grid = grid; Parent = grid; } protected override Brush? GetCellBackground(CoreRow row, DynamicColumnBase column) { var status = row.Get(x => x.Status); var color = status == KanbanStatus.Open ? Colors.Orange : status == KanbanStatus.InProgress ? Colors.Plum : status == KanbanStatus.Waiting ? Colors.LightGreen : Colors.Silver; return color.ToBrush(0.5); } } protected override IDynamicGridUIComponent CreateUIComponent() { return new UIComponent(this); } protected override void Init() { AddButton("Check for Updates", PRSDesktop.Resources.autoupdate.AsBitmapImage(), CheckForUpdates); AddButton("Open Support Session", PRSDesktop.Resources.appicon.AsBitmapImage(), OpenSupportSession); } private bool OpenSupportSession(Button button, CoreRow[] rows) { SupportUtils.OpenSupportSession(); return false; } private bool CheckForUpdates(Button button, CoreRow[] rows) { if (!SupportUtils.CheckForUpdates()) { if (MessageWindow.ShowYesNo( "You appear to be using the latest version already!\n\nRun the installer anyway?", "Update")) { SupportUtils.DownloadAndRunInstaller(); } } return false; } protected override void DoReconfigure(DynamicGridOptions options) { options.Clear(); options.AddRows = true; options.EditRows = true; options.FilterRows = true; options.HideDatabaseFilters = true; } private void BuildMenu(DynamicMenuColumn column, CoreRow? row) { if (row is null) return; var menu = column.GetMenu(); menu.AddItem("Add note", null, row, AddNote_Click); menu.AddItem("Attach system logs", null, row, AttachLogs_Click); menu.AddSeparator(); menu.AddItem("Close issue", null, row, CloseTask_Click); } private void AttachLogs_Click(CoreRow row) { var logFile = CoreUtils.GetLogFile(); var data = File.ReadAllBytes(logFile); var doc = new Document(); doc.Data = data; doc.CRC = CoreUtils.CalculateCRC(data); doc.FileName = Path.GetFileName(logFile); doc.TimeStamp = File.GetLastWriteTime(logFile); ClientFactory.Save(doc, "Attached logs to task."); var kanbanDocument = new KanbanDocument(); kanbanDocument.DocumentLink.CopyFrom(doc); kanbanDocument.EntityLink.CopyFrom(row.ToObject()); ClientFactory.Save(kanbanDocument, "Attached logs to task."); } public override Kanban CreateItem() { var item = base.CreateItem(); item.UserProperties["CustomerID"] = CustomerID.ToString(); item.Notes = [ $"Created on PRS {CoreUtils.GetVersion()} by {App.EmployeeName} ({App.EmployeeEmail})" ]; // item.Status = KanbanStatus.Open; return item; } private void AddNote_Click(CoreRow row) { var kanban = row.ToObject(); var text = ""; if(TextBoxDialog.Execute("Enter note:", ref text)) { text = string.Format("{0:yyyy-MM-dd HH:mm:ss}: {1}", DateTime.Now, text); kanban.Notes = kanban.Notes.Concatenate([text]); kanban.Status = KanbanStatus.InProgress; SaveItem(kanban); Refresh(false, true); } } private void CloseTask_Click(CoreRow row) { var kanban = row.ToObject(); kanban.Completed = DateTime.Now; kanban.Closed = DateTime.Now; SaveItem(kanban); Refresh(false, true); } private Column[] AllowedColumns = [ new(x => x.Number), new(x => x.Title), new(x => x.Description), new(x => x.Notes)]; protected override void CustomiseEditor(Kanban[] items, DynamicGridColumn column, BaseEditor editor) { base.CustomiseEditor(items, column, editor); if(!AllowedColumns.Any(x => x.Property == column.ColumnName)) { editor.Editable = editor.Editable.Combine(Editable.Hidden); } } public virtual CoreTable LookupValues(DataLookupEditor editor, Type parent, string columnname, BaseObject[]? items) { var client = ClientFactory.Create(editor.Type); var filter = LookupFactory.DefineLookupFilter(parent, editor.Type, columnname, items ?? (Array.CreateInstance(parent, 0) as BaseObject[])!); var columns = LookupFactory.DefineLookupColumns(parent, editor.Type, columnname); foreach (var key in editor.OtherColumns.Keys) columns.Add(key); var sort = LookupFactory.DefineSort(editor.Type); var result = client.Query(filter, columns, sort); result.Columns.Add(new CoreColumn { ColumnName = "Display", DataType = typeof(string) }); foreach (var row in result.Rows) { row["Display"] = LookupFactory.FormatLookup(parent, editor.Type, row, columnname); } return result; } protected override void DefineLookups(ILookupEditorControl sender, Kanban[] items, bool async = true) { if (sender.EditorDefinition is not DataLookupEditor editor) { base.DefineLookups(sender, items, async: async); return; } var colname = sender.ColumnName; if (async) { Task.Run(() => { try { var values = LookupValues(editor, typeof(Kanban), colname, items); Dispatcher.Invoke( () => { try { //Logger.Send(LogType.Information, typeof(T).Name, "Dispatching Results" + colname); sender.LoadLookups(values); } catch (Exception e2) { Logger.Send(LogType.Information, typeof(Kanban).Name, "Exception (2) in LoadLookups: " + e2.Message + "\n" + e2.StackTrace); } } ); } catch (Exception e) { Logger.Send(LogType.Information, typeof(Kanban).Name, "Exception (1) in LoadLookups: " + e.Message + "\n" + e.StackTrace); } }); } else { var values = LookupValues(editor, typeof(Kanban), colname, items); sender.LoadLookups(values); } } public override DynamicEditorPages LoadEditorPages(Kanban item) { var pages = new DynamicEditorPages { new DynamicDocumentGrid { Client = ClientFactory } }; return pages; } protected override DynamicGridColumns LoadColumns() { var columns = new DynamicGridColumns(); columns.Add(x => x.Number, caption: "Ticket", width: 60, alignment: Alignment.MiddleCenter); columns.Add(x => x.Title); columns.Add(x => x.CreatedBy, caption: "Created By", width: 150); columns.Add(x => x.EmployeeLink.Name, caption: "Assigned To", width: 150); columns.Add(x => x.Type.Description, caption: "Type", width: 100, alignment: Alignment.MiddleCenter); columns.Add(x => x.Status, caption: "Status", width: 80, alignment: Alignment.MiddleCenter); return columns; } #region Grid Stuff protected override string FormatRecordCount(int count) { return IsPaging ? $"{base.FormatRecordCount(count)} (loading..)" : base.FormatRecordCount(count); } protected override void Reload( Filters criteria, Columns columns, ref SortOrder? sort, CancellationToken token, Action action) { criteria.Add(new Filter(x => x.Closed).IsEqualTo(Guid.Empty)); criteria.Add(new Filter(x => x.Status).IsNotEqualTo(KanbanStatus.Complete)); criteria.Add(new Filter(x => x.JobLink.Customer.ID).IsEqualTo(CustomerID)); //criteria.Add(new Filter(CustomerProperty).IsEqualTo(CustomerID.ToString())); if(Options.PageSize > 0) { var inSort = sort; Task.Run(() => { var page = CoreRange.Database(Options.PageSize); var filter = criteria.Combine(); IsPaging = true; while (!token.IsCancellationRequested) { try { var data = KanbanClient.Query(filter, columns, inSort, page); data.Offset = page.Offset; IsPaging = data.Rows.Count == page.Limit; if (token.IsCancellationRequested) { break; } action(data, null); if (!IsPaging) break; // Proposal - Let's slow it down a bit to enhance UI responsiveness? Thread.Sleep(100); page.Next(); } catch (Exception e) { action(null, e); break; } } }, token); } else { KanbanClient.Query(criteria.Combine(), columns, sort, null, action); } } public override Kanban[] LoadItems(IList rows) { var results = new List(rows.Count); for (var i = 0; i < rows.Count; i += ChunkSize) { var chunk = rows.Skip(i).Take(ChunkSize); var filter = new Filter(x => x.ID).InList(chunk.Select(x => x.Get(x => x.ID)).ToArray()); var columns = DynamicGridUtils.LoadEditorColumns(Columns.None()); var data = KanbanClient.Query(filter, columns); results.AddRange(data.ToObjects()); } return results.ToArray(); } public override Kanban LoadItem(CoreRow row) { var id = row.Get(x => x.ID); return KanbanClient.Query( new Filter(x => x.ID).IsEqualTo(id), DynamicGridUtils.LoadEditorColumns(Columns.None())).ToObjects().FirstOrDefault() ?? throw new Exception($"No Kanban with ID {id}"); } public override void SaveItem(Kanban item) { CheckJob(item); KanbanClient.Save(item, "Edited by User"); } private void CheckJob(Kanban item) { if (item.ID == Guid.Empty) { item.CreatedBy = App.EmployeeName; // Check if there is an open Project Job (ie installation or periodic billing) for this Client var job = JobClient.Query( new Filter(x => x.Customer.ID).IsEqualTo(CustomerID) .And(x => x.JobType).IsEqualTo(JobType.Project) .And(x => x.JobStatus.Active).IsEqualTo(true), Columns.None() .Add(x => x.ID) .Add(x=>x.DefaultScope.ID) ).ToObjects().FirstOrDefault(); // No Job ? Create a service job for this ticket if (job == null) { job = new Job(); job.Name = item.Title; job.Customer.ID = CustomerID; job.JobType = JobType.Service; job.Notes = item.Notes?.ToList().ToArray() ?? []; job.UserProperties.Clear(); JobClient.Save(job, "Created by Client Issues Screen"); } // Created Tickets should always have a job #! item.JobLink.ID = job.ID; item.JobScope.ID = job.DefaultScope.ID; } } public override void SaveItems(IEnumerable items) { var list = items.ToArray(); foreach (var item in list) CheckJob(item); KanbanClient.Save(list, "Edited by User"); } public override void DeleteItems(params CoreRow[] rows) { var deletes = new List(); foreach (var row in rows) { var delete = new Kanban { ID = row.Get(x => x.ID) }; deletes.Add(delete); } KanbanClient.Delete(deletes, "Deleted on User Request"); } #endregion }