diff --git a/ModernUI/BuildingCustomControls/CodeSnippets/Lesson1.1/Step2/record-insights-panel.ts b/ModernUI/BuildingCustomControls/CodeSnippets/Lesson1.1/Step2/record-insights-panel.ts new file mode 100644 index 00000000..12da8ec9 --- /dev/null +++ b/ModernUI/BuildingCustomControls/CodeSnippets/Lesson1.1/Step2/record-insights-panel.ts @@ -0,0 +1,79 @@ +import { EventAggregator } from "aurelia-event-aggregator"; +import { bindable, Disposable } from "aurelia-framework"; +import { autoinject, IScreenApiResult } from "client-controls"; + +export class RecordInsightsPanelCustomElement { + @bindable title: string = "Record Insights Panel"; + @bindable status: string = ""; + @bindable warningMessage: string = ""; + @bindable details: string = ""; + @bindable collapsed: boolean = false; + @bindable accent: string = "neutral"; + + screenCaption: string = ""; + lastScreenUpdate: string = ""; + hasWarning: boolean = false; + + @autoinject + public eventAggregator!: EventAggregator; + + private subscriptions: Disposable[] = []; + + attached() { + this.hasWarning = !!this.warningMessage?.trim(); + + this.subscriptions.push( + this.eventAggregator.subscribe( + "screen-updated", + (screenData: IScreenApiResult) => { + this.screenCaption = `Screen ${screenData.screenID}`; + this.lastScreenUpdate = new Date().toLocaleTimeString(); + } + ) + ); + } + + detached() { + this.subscriptions.forEach(s => s?.dispose()); + this.subscriptions = []; + } + + warningMessageChanged(newValue: string) { + this.hasWarning = !!newValue?.trim(); + } + + toggleDetails() { + this.collapsed = !this.collapsed; + } + + dismissWarning() { + this.warningMessage = ""; + this.hasWarning = false; + } + + get statusClass(): string { + switch ((this.accent || "").toLowerCase()) { + case "success": + return "accent-success"; + case "warning": + return "accent-warning"; + case "danger": + return "accent-danger"; + default: + return "accent-neutral"; + } + } + + get badgeClass(): string { + switch ((this.accent || "").toLowerCase()) { + case "success": + return "badge-success"; + case "warning": + return "badge-warning"; + case "danger": + return "badge-danger"; + default: + return "badge-neutral"; + } + } +} \ No newline at end of file diff --git a/ModernUI/BuildingCustomControls/CodeSnippets/Lesson1.1/Step3/record-insights-panel.html b/ModernUI/BuildingCustomControls/CodeSnippets/Lesson1.1/Step3/record-insights-panel.html new file mode 100644 index 00000000..7b279ab3 --- /dev/null +++ b/ModernUI/BuildingCustomControls/CodeSnippets/Lesson1.1/Step3/record-insights-panel.html @@ -0,0 +1,42 @@ + + + + + + + + ${title} + + ${status} + + + + + ${screenCaption} + + • Updated ${lastScreenUpdate} + + + + + + + + + + ${warningMessage} + + + + + + ${details} + + + \ No newline at end of file diff --git a/ModernUI/BuildingCustomControls/CodeSnippets/Lesson1.1/Step4/record-insights-panel.scss b/ModernUI/BuildingCustomControls/CodeSnippets/Lesson1.1/Step4/record-insights-panel.scss new file mode 100644 index 00000000..027cbdb3 --- /dev/null +++ b/ModernUI/BuildingCustomControls/CodeSnippets/Lesson1.1/Step4/record-insights-panel.scss @@ -0,0 +1,112 @@ +.record-insights-panel { + position: relative; + box-sizing: border-box; + border: 1px solid #d6d9de; + border-left-width: 5px; + border-radius: 8px; + padding: 14px 16px; + margin: 0 0 16px 0; + background: #fff; +} + +.record-insights-panel.accent-warning { + border-left-color: #b26a00; +} + +.record-insights-panel.accent-success { + border-left-color: #2e8540; +} + +.record-insights-panel.accent-danger { + border-left-color: #c62828; +} + +.record-insights-panel.accent-neutral { + border-left-color: #7a869a; +} + +.panel-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 16px; +} + +.panel-header-main { + flex: 1 1 auto; + min-width: 0; +} + +.panel-title-row { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 6px; +} + +.panel-title { + margin: 0; + font-size: 16px; + font-weight: 600; + line-height: 1.25; +} + +.panel-subtitle { + font-size: 12px; + line-height: 1.4; + color: #5f6b7a; +} + +.status-badge { + display: inline-block; + padding: 2px 8px; + border-radius: 999px; + font-size: 12px; + font-weight: 600; + line-height: 1.2; + white-space: nowrap; +} + +.badge-neutral { + background: #eef2f7; + color: #5f6b7a; +} + +.badge-success { + background: #e6f4ea; + color: #2e8540; +} + +.badge-warning { + background: #fff4db; + color: #b26a00; +} + +.badge-danger { + background: #fdecea; + color: #c62828; +} + +.warning-box { + margin-top: 12px; + padding: 10px 12px; + background: #fff4db; + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.panel-body { + margin-top: 12px; +} + +.details-block { + margin: 0; + line-height: 1.5; +} + +.toggle-button, +.dismiss-button { + white-space: nowrap; +} \ No newline at end of file diff --git a/ModernUI/BuildingCustomControls/CodeSnippets/Lesson1.2/Step1/RSSVWorkOrderEntry.cs b/ModernUI/BuildingCustomControls/CodeSnippets/Lesson1.2/Step1/RSSVWorkOrderEntry.cs new file mode 100644 index 00000000..1a690c81 --- /dev/null +++ b/ModernUI/BuildingCustomControls/CodeSnippets/Lesson1.2/Step1/RSSVWorkOrderEntry.cs @@ -0,0 +1,443 @@ +using PX.Common; +using PX.Data; +using PX.Data.BQL; +using PX.Data.BQL.Fluent; +using PX.Data.WorkflowAPI; +using PX.Objects.AR; +using PX.Objects.Common; +using PX.Objects.IN; +using PX.Objects.SO; +using System; +using System.Collections; +using System.Collections.Generic; + + +namespace PhoneRepairShop +{ + public class RSSVWorkOrderEntry : PXGraph + { + #region Views + + //The primary view + public SelectFrom.View WorkOrders = null!; + + //The view for the Repair Items tab + public SelectFrom. + Where>.View + RepairItems = null!; + + //The view for the Labor tab + public SelectFrom. + Where>.View + Labor = null!; + + //The view for the auto-numbering of records + public PXSetup AutoNumSetup = null!; + #endregion + + #region Graph constructor + public RSSVWorkOrderEntry() + { + RSSVSetup setup = AutoNumSetup.Current; + } + #endregion + + //////////////////////// The added code + + #region Attached Fields + // Acuminator disable once PX1016 ExtensionDoesNotDeclareIsActiveMethod extension + // should be constantly active + public class panelStatus : + PXFieldAttachedTo.By.AsString.Named + { + public override string? GetValue(RSSVWorkOrder row) => + PXMessages.LocalizeNoPrefix(GetPanelStatus(row)); + + private static string? GetPanelStatus(RSSVWorkOrder row) + { + if (row == null) + return null; + + if (row.Status == WorkOrderStatusConstants.OnHold || + row.Assignee == null || + row.Status == WorkOrderStatusConstants.ReadyForAssignment) + return PanelStatus.NeedsAttention; + + if (row.InvoiceNbr == null && + row.Status == WorkOrderStatusConstants.Assigned) + return PanelStatus.InProgress; + + if (row.InvoiceNbr == null && + row.Status == WorkOrderStatusConstants.Completed) + return PanelStatus.Completed; + + return PanelStatus.Invoiced; + } + } + + // Acuminator disable once PX1016 ExtensionDoesNotDeclareIsActiveMethod extension + // should be constantly active + public class panelWarningMessage : + PXFieldAttachedTo.By.AsString.Named + { + public override string? GetValue(RSSVWorkOrder row) => + PXMessages.LocalizeNoPrefix(GetPanelWarningMessage(row)); + + private static string? GetPanelWarningMessage(RSSVWorkOrder row) + { + if (row == null) + return null; + + if (row.Status == WorkOrderStatusConstants.OnHold) + return PanelWarningMessages.OnHoldWarning; + + if (row.Assignee == null || + row.Status == WorkOrderStatusConstants.ReadyForAssignment) + return PanelWarningMessages.NoAssigneeWarning; + + return null; + } + + } + + // Acuminator disable once PX1016 ExtensionDoesNotDeclareIsActiveMethod extension + // should be constantly active + public class panelDetails : + PXFieldAttachedTo.By.AsString.Named + { + public override string? GetValue(RSSVWorkOrder row) => + PXMessages.LocalizeNoPrefix(GetPanelDetails(row)); + + private static string? GetPanelDetails(RSSVWorkOrder row) + { + if (row == null) + return null; + + if (row.Status == WorkOrderStatusConstants.OnHold) + return PanelDetailsMessages.OnHoldDetails; + + if (row.Assignee == null || + row.Status == WorkOrderStatusConstants.ReadyForAssignment) + return PanelDetailsMessages.NoAssigneeDetails; + + if (row.Status == WorkOrderStatusConstants.Assigned) + return PanelDetailsMessages.InProgressDetails; + + if (row.InvoiceNbr == null && + row.Status == WorkOrderStatusConstants.Completed) + return PanelDetailsMessages.CompleteDetails; + + return PanelDetailsMessages.ReadyDetails; + } + } + + // Acuminator disable once PX1016 ExtensionDoesNotDeclareIsActiveMethod extension + // should be constantly active + public class panelAccent : + PXFieldAttachedTo.By.AsString.Named + { + public override string? GetValue(RSSVWorkOrder row) => + GetPanelAccent(row); + + private static string? GetPanelAccent(RSSVWorkOrder row) + { + if (row == null) + return null; + + if (row.Status == WorkOrderStatusConstants.OnHold || + row.Status == WorkOrderStatusConstants.ReadyForAssignment || + row.Assignee == null) + return PanelAccent.Danger; + + if (row.Status == WorkOrderStatusConstants.Assigned) + return PanelAccent.Warning; + + if (row.InvoiceNbr == null && + row.Status == WorkOrderStatusConstants.Completed) + return PanelAccent.Neutral; + + return PanelAccent.Success; + } + + } + #endregion + + //////////////////////// The end of the added code + + public PXFilter MasterView; + public PXFilter DetailsView; + + [Serializable] + public class MasterTable : PXBqlTable, IBqlTable + { + + } + + [Serializable] + public class DetailsTable : PXBqlTable, IBqlTable + { + + } + + #region Events + //Copy repair items and labor items from the Services and Prices form. + protected virtual void _(Events.RowUpdated e) + { + if (WorkOrders.Cache.GetStatus(e.Row) != PXEntryStatus.Inserted || + e.Cache.ObjectsEqual(e.Row, e.OldRow)) + return; + + if (e.Row.ServiceID == null || e.Row.DeviceID == null || + IsCopyPasteContext || RepairItems.Select().Count != 0 || + Labor.Select().Count != 0) + return; + + //Retrieve the default repair items + var repairItems = SelectFrom. + Where. + And>> + .View.Select(this); + //Insert default repair items + foreach (RSSVRepairItem item in repairItems) + { + RSSVWorkOrderItem orderItem = RepairItems.Insert(); + orderItem.RepairItemType = item.RepairItemType; + orderItem.InventoryID = item.InventoryID; + orderItem.BasePrice = item.BasePrice; + RepairItems.Update(orderItem); + } + + //Retrieve the default labor items + var laborItems = SelectFrom. + Where. + And>> + .View.Select(this); + //Insert the default labor items + foreach (RSSVLabor item in laborItems) + { + RSSVWorkOrderLabor orderItem = new RSSVWorkOrderLabor(); + orderItem.InventoryID = item.InventoryID; + orderItem = Labor.Insert(orderItem); + orderItem.DefaultPrice = item.DefaultPrice; + orderItem.Quantity = item.Quantity; + orderItem.ExtPrice = item.ExtPrice; + Labor.Update(orderItem); + } + } + + //Update price and repair item type when inventory ID of repair item is updated. + protected void _(Events.FieldUpdated e) + { + RSSVWorkOrderItem row = e.Row; + if (row.InventoryID != null && row.RepairItemType == null) + { + //Use the PXSelector attribute to select the stock item. + var item = PXSelectorAttribute.Select< + RSSVWorkOrderItem.inventoryID>(e.Cache, row) as InventoryItem; + //Copy the repair item type from the stock item to the row. + var itemExt = item?.GetExtension(); + if (itemExt != null) row.RepairItemType = itemExt.UsrRepairItemType; + } + e.Cache.SetDefaultExt(e.Row); + } + + protected void _(Events.FieldDefaulting e) + { + RSSVWorkOrderItem row = e.Row; + if (row.InventoryID == null) return; + //Use the PXSelector attribute to select the stock item. + var item = PXSelectorAttribute.Select(e.Cache, row) as InventoryItem; + //Retrieve the base price for the stock item. + var curySettings = InventoryItemCurySettings.PK.Find( + this, item?.InventoryID, Accessinfo.BaseCuryID ?? "USD"); + //Copy the base price from the stock item to the row. + if (curySettings != null) e.NewValue = curySettings.BasePrice; + } + + //Validate that Quantity is greater than or equal to 0 and + //correct the value to the default if the value is less than the default. + protected virtual void _(Events.FieldVerifying e) + { + if (e.Row == null || e.NewValue == null) return; + + if ((decimal)e.NewValue < 0) + { + //Throwing an exception to cancel the assignment + //of the new value to the field + throw new PXSetPropertyException(e.Row, + Messages.QuantityCannotBeNegative); + } + + var workOrder = WorkOrders.Current; + if (workOrder != null) + { + //Retrieving the default labor item related to the work order labor + RSSVLabor labor = SelectFrom. + Where. + And>. + And>> + .View.Select(this, workOrder.ServiceID, workOrder.DeviceID, + e.Row.InventoryID); + if (labor != null && (decimal)e.NewValue < labor.Quantity) + { + //Correcting the LineQty value + e.NewValue = labor.Quantity; + //Raising the ExceptionHandling event for the Quantity field + //to attach the exception object to the field + e.Cache.RaiseExceptionHandling( + e.Row, e.NewValue, new PXSetPropertyException(e.Row, + Messages.QuantityTooSmall, PXErrorLevel.Warning)); + } + } + } + + //Display an error if the priority is too low for the selected service + protected virtual void _(Events.RowUpdating e) + { + // The modified data record (not in the cache yet) + RSSVWorkOrder row = e.NewRow; + // The data record that is stored in the cache + RSSVWorkOrder originalRow = e.Row; + + if (!e.Cache.ObjectsEqual(row, originalRow)) + { + if (row.Priority == WorkOrderPriorityConstants.Low) + { + //Obtain the service record + RSSVRepairService service = SelectFrom. + Where>. + View.Select(this, row.ServiceID); + + if (service != null && service.PreliminaryCheck == true) + { + //Display the error for the Priority field + WorkOrders.Cache.RaiseExceptionHandling< + RSSVWorkOrder.priority>(row, originalRow.Priority, + new PXSetPropertyException(row, Messages.PriorityTooLow)); + + //Assign the proper priority + e.NewRow.Priority = WorkOrderPriorityConstants.Medium; + } + } + } + } + #endregion + + #region Actions + public PXAction ReleaseFromHold = null!; + [PXButton(), PXUIField(DisplayName = "Remove Hold", + MapEnableRights = PXCacheRights.Select, + MapViewRights = PXCacheRights.Select)] + protected virtual IEnumerable releaseFromHold(PXAdapter adapter) + => adapter.Get(); + + public PXAction PutOnHold = null!; + [PXButton, PXUIField(DisplayName = "Hold", + MapEnableRights = PXCacheRights.Select, + MapViewRights = PXCacheRights.Select)] + protected virtual IEnumerable putOnHold(PXAdapter adapter) => adapter.Get(); + + public PXAction Assign = null!; + [PXButton] + [PXUIField(DisplayName = "Assign", Enabled = false)] + protected virtual IEnumerable assign(PXAdapter adapter) => adapter.Get(); + + public PXAction Complete = null!; + [PXButton] + [PXUIField(DisplayName = "Complete", Enabled = false)] + protected virtual IEnumerable complete(PXAdapter adapter) => adapter.Get(); + + private static void CreateInvoice(RSSVWorkOrder workOrder) + { + using (var ts = new PXTransactionScope()) + { + // Create an instance of the SOInvoiceEntry graph. + var invoiceEntry = PXGraph.CreateInstance(); + // Initialize the summary of the invoice. + var doc = new ARInvoice() + { + DocType = ARDocType.Invoice + }; + doc = invoiceEntry.Document.Insert(doc); + doc.CustomerID = workOrder.CustomerID; + invoiceEntry.Document.Update(doc); + + // Create an instance of the RSSVWorkOrderEntry graph. + var workOrderEntry = + PXGraph.CreateInstance(); + workOrderEntry.WorkOrders.Current = workOrder; + + // Add the lines associated with the repair items + // (from the Repair Items tab). + foreach (RSSVWorkOrderItem line in + workOrderEntry.RepairItems.Select()) + { + var repairTran = invoiceEntry.Transactions.Insert(); + repairTran.InventoryID = line.InventoryID; + repairTran.Qty = 1; + repairTran.CuryUnitPrice = line.BasePrice; + invoiceEntry.Transactions.Update(repairTran); + } + // Add the lines associated with labor (from the Labor tab). + foreach (RSSVWorkOrderLabor line in + workOrderEntry.Labor.Select()) + { + var laborTran = invoiceEntry.Transactions.Insert(); + laborTran.InventoryID = line.InventoryID; + laborTran.Qty = line.Quantity; + laborTran.CuryUnitPrice = line.DefaultPrice; + laborTran.CuryExtPrice = line.ExtPrice; + invoiceEntry.Transactions.Update(laborTran); + } + + // Save the invoice to the database. + invoiceEntry.Actions.PressSave(); + + // Assign the generated invoice number and save the changes. + workOrder.InvoiceNbr = invoiceEntry.Document.Current.RefNbr; + workOrderEntry.WorkOrders.Update(workOrder); + workOrderEntry.Actions.PressSave(); + + ts.Complete(); + } + } + + public PXAction CreateInvoiceAction = null!; + [PXButton] + [PXUIField(DisplayName = "Create Invoice", Enabled = true)] + protected virtual IEnumerable createInvoiceAction(PXAdapter adapter) + { + // Populate a local list variable. + List list = new List(); + foreach (RSSVWorkOrder order in adapter.Get()) + { + list.Add(order); + } + + // Trigger the Save action to save changes in the database. + Actions.PressSave(); + + var workOrder = WorkOrders.Current; + PXLongOperation.StartOperation(this, delegate () { + CreateInvoice(workOrder); + }); + + // Return the local list variable. + return list; + } + #endregion + + #region Workflow Event Handlers + public PXWorkflowEventHandler OnCloseDocument + = null!; + public PXWorkflowEventHandler OnInvoiceGotPrepaid + = null!; + #endregion + + } +} \ No newline at end of file diff --git a/ModernUI/BuildingCustomControls/CodeSnippets/Lesson1.2/Step2/Constants.cs b/ModernUI/BuildingCustomControls/CodeSnippets/Lesson1.2/Step2/Constants.cs new file mode 100644 index 00000000..5b145995 --- /dev/null +++ b/ModernUI/BuildingCustomControls/CodeSnippets/Lesson1.2/Step2/Constants.cs @@ -0,0 +1,109 @@ +using PX.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace PhoneRepairShop +{ + public static class RepairComplexity + { + public const string Low = "L"; + public const string Medium = "M"; + public const string High = "H"; + } + + //Constants for the repair item types + public static class RepairItemTypeConstants + { + public const string Battery = "BT"; + public const string Screen = "SR"; + public const string ScreenCover = "SC"; + public const string BackCover = "BC"; + public const string Motherboard = "MB"; + } + + //Constants for the priority of repair work orders + public static class WorkOrderPriorityConstants + { + public const string High = "H"; + public const string Medium = "M"; + public const string Low = "L"; + } + + //Constants for the statuses of repair work orders + public static class WorkOrderStatusConstants + { + public const string OnHold = "OH"; + public const string PendingPayment = "PP"; + public const string ReadyForAssignment = "RA"; + public const string Assigned = "AS"; + public const string Completed = "CM"; + public const string Paid = "PD"; + } + + //Constants for the repair work order types + public static class WorkOrderTypeConstants + { + public const string Simple = "SP"; + public const string Standard = "ST"; + public const string Awaiting = "AW"; + } + + //////////////////////// The added code + + // Constants for the Record Insight Panel's status property + [PXLocalizable] + public static class PanelStatus + { + public const string NeedsAttention = "Needs Attention"; + public const string InProgress = "In Progress"; + public const string Completed = "Completed"; + public const string Invoiced = "Invoiced"; + } + + // Constants for the Record Insight Panel's warning message property + [PXLocalizable] + public static class PanelWarningMessages + { + public const string OnHoldWarning = + "The repair work order is on hold and must be reviewed before further processing."; + + public const string NoAssigneeWarning = + "No assignee has been selected for this repair work order."; + } + + // Constants for the Record Insight Panel's details property + [PXLocalizable] + public static class PanelDetailsMessages + { + public const string OnHoldDetails = + "Check the priority and required repair items before removing the hold."; + + public const string NoAssigneeDetails = + "Assign an employee to this repair work order."; + + public const string InProgressDetails = + "The repair work order is being processed and has not been completed yet."; + + public const string CompleteDetails = + "The repair work order has been completed but has not yet been linked to an invoice."; + + public const string ReadyDetails = + "The repair work order has been completed and has been linked to an invoice."; + } + + // Constants for the Record Insight Panel's accent property + public static class PanelAccent + { + public const string Danger = "danger"; + public const string Neutral = "neutral"; + public const string Warning = "warning"; + public const string Success = "success"; + } + + //////////////////////// The end of the added code + + +} diff --git a/ModernUI/BuildingCustomControls/CodeSnippets/Lesson1.2/Step3/RS301000_PhoneRepairShop_RecordInsights.ts b/ModernUI/BuildingCustomControls/CodeSnippets/Lesson1.2/Step3/RS301000_PhoneRepairShop_RecordInsights.ts new file mode 100644 index 00000000..9ca7cf43 --- /dev/null +++ b/ModernUI/BuildingCustomControls/CodeSnippets/Lesson1.2/Step3/RS301000_PhoneRepairShop_RecordInsights.ts @@ -0,0 +1,17 @@ +import { PXFieldState } from "client-controls"; +import { + RS301000, + RSSVWorkOrder +} from "src/customizationScreens/Company/screens/RS/RS301000/RS301000"; + +export interface RS301000_PhoneRepairShop_RecordInsights extends RS301000 {} +export class RS301000_PhoneRepairShop_RecordInsights {} + +export interface RSSVWorkOrder_PhoneRepairShop_RecordInsights + extends RSSVWorkOrder {} +export class RSSVWorkOrder_PhoneRepairShop_RecordInsights { + PanelStatus: PXFieldState; + PanelWarningMessage: PXFieldState; + PanelDetails: PXFieldState; + PanelAccent: PXFieldState; +} \ No newline at end of file diff --git a/ModernUI/BuildingCustomControls/CodeSnippets/Lesson1.2/Step4/RS301000_PhoneRepairShop_RecordInsights.html b/ModernUI/BuildingCustomControls/CodeSnippets/Lesson1.2/Step4/RS301000_PhoneRepairShop_RecordInsights.html new file mode 100644 index 00000000..bd851ce7 --- /dev/null +++ b/ModernUI/BuildingCustomControls/CodeSnippets/Lesson1.2/Step4/RS301000_PhoneRepairShop_RecordInsights.html @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/ModernUI/BuildingCustomControls/CodeSnippets/Lesson2.1/Step5/RS301000_PhoneRepairShop_RepairItemsEvents.ts b/ModernUI/BuildingCustomControls/CodeSnippets/Lesson2.1/Step5/RS301000_PhoneRepairShop_RepairItemsEvents.ts new file mode 100644 index 00000000..741463fd --- /dev/null +++ b/ModernUI/BuildingCustomControls/CodeSnippets/Lesson2.1/Step5/RS301000_PhoneRepairShop_RepairItemsEvents.ts @@ -0,0 +1,26 @@ +import { + CustomEventType, + handleEvent, + RowCssHandlerArgs, +} from "client-controls"; + +import { + RS301000, + RSSVWorkOrderItem +} from "src/customizationScreens/Company/screens/RS/RS301000/RS301000"; + +export interface RS301000_PhoneRepairShop_RepairItemsEvents + extends RS301000 {} + +export class RS301000_PhoneRepairShop_RepairItemsEvents { + @handleEvent(CustomEventType.GetRowCss, { view: "RepairItems" as never}) + // "as never" works around a TypeScript inference issue with the decorator. + // Runtime value is still "RepairItems". + getRepairItemsRowCss(args: RowCssHandlerArgs): string { + const item = args?.selector?.row; + if (item != null && item.BasePrice.value > 15) { + return "bold-row"; + } + return undefined; + } +} \ No newline at end of file diff --git a/ModernUI/BuildingCustomControls/Prompts/Lesson2.1/Step3/Prompt_GenerateEventHandler.txt b/ModernUI/BuildingCustomControls/Prompts/Lesson2.1/Step3/Prompt_GenerateEventHandler.txt new file mode 100644 index 00000000..c5e12758 --- /dev/null +++ b/ModernUI/BuildingCustomControls/Prompts/Lesson2.1/Step3/Prompt_GenerateEventHandler.txt @@ -0,0 +1,45 @@ +We are working on an Acumatica Modern UI customization for the Repair Work Orders +form (RS301000). + +The form has a RepairItems table (grid) that displays repair items for the current repair +work order. + +Create an event handler that highlights repair item rows that require attention. + +For this activity, a row should be highlighted when the repair item price is +greater than 15. + +Use the Modern UI event-handler pattern with the handleEvent decorator. + +The event handler should use: +@handleEvent(CustomEventType.GetRowCss, { view: "RepairItems" }) + +The handler should return the name of a predefined CSS class when a row should be +highlighted and return undefined when no custom CSS class should be applied. + +The event handler should target the RepairItems view. + +The row type is RSSVWorkOrderItem. + +Use the BasePrice field of the row to determine whether the row should be +highlighted. + +Follow these constraints: +- Use @handleEvent and CustomEventType.GetRowCss. +- Use RowCssHandlerArgs for the handler argument. +- Do not add backend code. +- Return a CSS class named bold-row when the row should be highlighted. +- Return undefined when the row should not be highlighted. +- Include any import statements that may be required. +- The proposed code must be added in a TS extension file of the RS301000 form. + This requires adding the RS301000 and RSSVWorkOrderItem imports. +- The handler code should be added in the RS301000_PhoneRepairShop_RepairItemsEvents + screen extension class. + +Please propose the TypeScript changes needed. + +In your response, include: +1. The required imports +2. The event handler method +3. A short explanation of how the handler works +4. Any assumptions or risks that should be reviewed by a developer \ No newline at end of file