using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using QuestPDF.Drawing; using QuestPDF.Fluent; using QuestPDF.Helpers; using QuestPDF.Infrastructure; namespace PSTW_CentralSystem.Areas.IT.Printing { public class ItRequestPdfService { public byte[] Generate(ItRequestReportModel m) { QuestPDF.Settings.License = LicenseType.Community; return Document.Create(container => { container.Page(page => { page.Size(PageSizes.A4); page.Margin(14); page.DefaultTextStyle(x => x.FontSize(7)); page.Content().Column(col => { col.Item().Element(e => HeaderStrip(e, m)); col.Item().PaddingTop(4).LineHorizontal(0.8f); col.Item().PaddingTop(4).Text( "Submit this form into http://support.transwater.com.my"); // ===== SECTION A ===== #region SECTION A col.Item().PaddingTop(4).Text("Section A").Bold().FontSize(7); col.Item().Element(e => SectionA_IdentityEmployment(e, m)); // two-column layout (left = Hardware+OS+Software, right = Email+Internet+Shared+Copier) col.Item().Table(t => { t.ColumnsDefinition(cols => { cols.RelativeColumn(1); // LEFT cols.ConstantColumn(8); // gutter cols.RelativeColumn(1); // RIGHT }); // left cell (entire left stack) t.Cell() .Element(x => x.MinHeight(380)) // optional: set a floor so both look balanced .Element(e => SectionA_HardwareOsSoftware(e, m)); // gutter cell t.Cell().Text(""); // right cell (entire right stack) t.Cell() .Element(x => x.MinHeight(380)) // same floor as left .Element(e => SectionA_RightPane_EmailInternetShared(e, m)); }); // -- Hardware + Email #region SECTION A • Hardware Requirements // Rendered together inside SectionA_HardwareEmail (Hardware block) #endregion #region SECTION A • Email // Rendered together inside SectionA_HardwareEmail (Email block) #endregion // -- Internet Permissions #region SECTION A • Internet Permissions #endregion // -- OS Requirements #region SECTION A • OS Requirements #endregion // -- Software & Shared Permissions #region SECTION A • Software (General, Utility, Custom) #endregion #region SECTION A • Shared Permissions #endregion // -- Copier (kept as-is) // -- Form Arrangement #region SECTION A • Form Arrangement col.Item().Element(e => FormArrangementBlock(e, m)); #endregion // -- Approvals table #endregion // SECTION A // ===== SECTION B ===== #region SECTION B col.Item().Element(e => SectionB_TwoBlocks(e, m)); // -- Asset Information #region SECTION B • Asset Information #endregion // -- Remarks #region SECTION B • Remarks #endregion // -- Signatures #region SECTION B • Requestor Acknowledgement / Completed By #endregion #endregion // SECTION B }); }); }).GeneratePdf(); } // ---------------- helpers & styles ---------------- #region HELPERS static string Box(bool on) => on ? "☒" : "☐"; static string F(DateTime? dt) => dt.HasValue ? dt.Value.ToString("dd/MM/yyyy", CultureInfo.InvariantCulture) : ""; static IContainer Cell(IContainer x) => x.Border(1).BorderColor(Colors.Grey.Lighten2).Padding(4); static IContainer CellHead(IContainer x) => x.Background(Colors.Grey.Lighten4).Border(1).BorderColor(Colors.Grey.Lighten2).Padding(4); static IContainer CellMandatory(IContainer x) => x.Background(Colors.Grey.Lighten2).Border(1).BorderColor(Colors.Grey.Lighten2).Padding(4); #endregion // ================= HEADER ================= #region HEADER void HeaderStrip(IContainer c, ItRequestReportModel m) { c.Row(row => { row.RelativeItem().Column(left => { left.Item().Text("I.T. REQUEST FORM").SemiBold().FontSize(14); left.Item().Text("(Group IT)").Italic().FontSize(10); }); row.ConstantItem(230).Column(right => { right.Item().Text($"Document No. {m.DocumentNo}").AlignRight(); right.Item().Text($"Effective Date {(m.EffectiveDate == default ? "" : m.EffectiveDate.ToString("dd/MM/yyyy"))}").AlignRight(); right.Item().Text($"Rev. No {m.RevNo}").AlignRight(); right.Item().Text($"Doc. Page No {m.DocPageNo}").AlignRight(); }); }); } #endregion // ================= SECTION A ================= #region SECTION A • Identity & Employment // ================= SECTION A – Identity & Employment (refined layout) ================= // SECTION A — Identity + Employment as ONE BLOCK (matches screenshot) // ================= SECTION A – Compact Unified Block (new refined layout) ================= void SectionA_IdentityEmployment(IContainer c, ItRequestReportModel m) { // helpers var emp = (m.EmploymentStatus ?? "").Trim().ToLowerInvariant(); bool isPerm = emp == "permanent"; bool isContract = emp == "contract"; bool isTemp = emp == "temp" || emp == "temporary"; bool isNew = emp == "new staff" || emp == "new"; IContainer L(IContainer x) => x.Border(1).BorderColor(Colors.Grey.Lighten2).Padding(4); IContainer V(IContainer x) => x.Border(1).BorderColor(Colors.Grey.Lighten2).Padding(4); c.Table(t => { // 6-column consistent grid t.ColumnsDefinition(cols => { cols.RelativeColumn(1); // L1 cols.RelativeColumn(2); // V1 cols.RelativeColumn(1); // L2 cols.RelativeColumn(2); // V2 cols.RelativeColumn(1); // L3 cols.RelativeColumn(2); // V3 }); // === Row 1: Staff Name / Company / Div === t.Cell().Element(L).Text("Staff Name:"); t.Cell().Element(V).Text(m.StaffName ?? ""); t.Cell().Element(L).Text("Company:"); t.Cell().Element(V).Text(m.CompanyName ?? ""); t.Cell().Element(L).Text("Div/Dept:"); t.Cell().Element(V).Text(m.DepartmentName ?? ""); // === Row 2: Designation / Location === t.Cell().Element(L).Text("Designation:"); t.Cell().Element(V).Text(m.Designation ?? ""); t.Cell().Element(L).Text("Location:"); t.Cell().Element(V).Text(m.Location ?? ""); // fill the last two cells to maintain full-width grid t.Cell().Element(L).Text(""); t.Cell().Element(V).Text(""); // === Row 3: Employment Status + Contract End Date beside it === t.Cell().Element(L).Text("Employment Status:"); t.Cell().ColumnSpan(3).Element(V).Row(r => { r.Spacing(12); r.ConstantItem(50).Text($"{Box(isPerm)} Permanent"); r.ConstantItem(50).Text($"{Box(isContract)} Contract"); r.ConstantItem(50).Text($"{Box(isTemp)} Temp"); r.ConstantItem(50).Text($"{Box(isNew)} New Staff"); }); t.Cell().Element(L).Text("If Temp / Contract End Date:"); t.Cell().Element(V).Text(F(m.ContractEndDate)); // === Row 4: Phone Ext + Required Date beside it === t.Cell().Element(L).Text("Phone Ext:"); t.Cell().Element(V).Text(m.PhoneExt ?? ""); t.Cell().Element(L).Text("Required Date:"); t.Cell().Element(V).Text(F(m.RequiredDate)); // keep remaining two cells empty to close grid t.Cell().Element(L).Text(""); t.Cell().Element(V).Text(""); }); } #endregion #region SECTION A • Hardware Requirements + Email // ===================== SECTION A – HARDWARE + OS + SOFTWARE ===================== void SectionA_HardwareOsSoftware(IContainer c, ItRequestReportModel m) { bool HasSoft(string name) => m.Software.Any(s => (s.Name ?? "").Equals(name, StringComparison.InvariantCultureIgnoreCase)); var general = new[] { "MS Word", "MS Excel", "MS Outlook", "MS PowerPoint", "MS Access", "MS Project", "Acrobat Standard", "AutoCAD", "Worktop/ERP Login" }; var utility = new[] { "PDF Viewer", "7Zip", "AutoCAD Viewer", "Smart Draw" }; c.Table(t => { t.ColumnsDefinition(cols => { cols.RelativeColumn(1); }); // --- HEADER: Hardware Requirements --- t.Cell().Element(x => x.Background(Colors.Black).Padding(3)) .Text("Hardware Requirements").FontColor(Colors.White).Bold(); // --- Hardware Body --- t.Cell().Border(1).Padding(1).Column(col => { col.Spacing(5); col.Item().Row(r => { // Left column: Purpose + Justification r.RelativeItem().Column(left => { left.Item().Text($"{Box(m.HwPurposeNewRecruitment)} New Staff Recruitment"); left.Item().Text($"{Box(m.HwPurposeReplacement)} Replacement"); left.Item().Text($"{Box(m.HwPurposeAdditional)} Additional"); left.Item().PaddingTop(5).Text("Justification (for hardware change) :"); left.Item().Border(1).Height(40).Padding(4) .Text(string.IsNullOrWhiteSpace(m.Justification) ? "" : m.Justification); }); // Right column: Category selection r.RelativeItem().Column(right => { right.Item().Text("Select below:"); right.Item().Text($"{Box(m.HwDesktopAllIn)} Desktop (all inclusive)"); right.Item().Text($"{Box(m.HwNotebookAllIn)} Notebook (all inclusive)"); right.Item().Text($"{Box(m.HwDesktopOnly)} Desktop only"); right.Item().Text($"{Box(m.HwNotebookOnly)} Notebook only"); right.Item().Text($"{Box(m.HwNotebookBattery)} Notebook battery"); right.Item().Text($"{Box(m.HwPowerAdapter)} Power Adapter"); right.Item().Text($"{Box(m.HwMouse)} Computer Mouse"); right.Item().Text($"{Box(m.HwExternalHdd)} External Hard Drive"); right.Item().Text("Other (Specify):"); right.Item().Border(1).Height(18).Padding(3) .Text(string.IsNullOrWhiteSpace(m.HwOtherText) ? "-" : m.HwOtherText); }); }); }); // --- HEADER: OS Requirements --- t.Cell().Element(x => x.Background(Colors.Black).Padding(3)) .Text("OS Requirements (Leave blank if no specific requirement)") .FontColor(Colors.White).Bold(); // --- OS Body --- t.Cell().Border(1).Padding(5).Column(col => { col.Item().Text("Requirements:"); col.Item().Border(1).Height(30).Padding(4) .Text(m.OsRequirements.Any() ? string.Join(Environment.NewLine, m.OsRequirements) : "-"); }); // --- HEADER: Software Requirements --- t.Cell().Element(x => x.Background(Colors.Black).Padding(3)) .Text("Software Requirements").FontColor(Colors.White).Bold(); // --- Software Body (3 columns) --- t.Cell().Border(1).Padding(5).Table(st => { st.ColumnsDefinition(cols => { cols.RelativeColumn(1); // General cols.RelativeColumn(1); // Utility cols.RelativeColumn(1); // Custom }); // Headings st.Header(h => { h.Cell().Element(CellHead).Text("General Software"); h.Cell().Element(CellHead).Text("Utility Software"); h.Cell().Element(CellHead).Text("Custom Software"); }); int maxRows = Math.Max(general.Length, utility.Length); for (int i = 0; i < maxRows; i++) { st.Cell().Element(Cell) .Text(i < general.Length ? $"{Box(HasSoft(general[i]))} {general[i]}" : ""); st.Cell().Element(Cell) .Text(i < utility.Length ? $"{Box(HasSoft(utility[i]))} {utility[i]}" : ""); if (i == 0) { st.Cell().Element(Cell).Text("Others (Specify) :"); } else if (i == 1) { st.Cell().Element(Cell).Border(1).Height(15).Padding(3).Text("-"); } else st.Cell().Element(Cell).Text(""); } }); }); } #endregion #region SECTION A • Internet Permissions // ===================== SECTION A – RIGHT PANE ===================== void SectionA_RightPane_EmailInternetShared(IContainer c, ItRequestReportModel m) { c.Column(col => { // ===== Email ===== col.Item().Element(x => x.Background(Colors.Black).Padding(3)) .Text("Email").FontColor(Colors.White).Bold(); col.Item().Border(1).Padding(6).Column(cc => { // single-line "Email Address:" with inline box cc.Item().Row(r => { r.ConstantItem(100).Text("Email Address:"); // label column width r.RelativeItem().Border(1).Height(16).PaddingHorizontal(4).AlignMiddle() .Text(m.Emails.Any() ? string.Join("; ", m.Emails) : ""); }); cc.Item().PaddingTop(4).Text("Arrangement Guide: FirstName&LastName or Name&Surname or Surname&Name."); cc.Item().Text("Full name and short form/ initial is allowed to be used if the name is too long."); cc.Item().Text("e.g. siti.nurhaliza, jackie.chan, ks.chan"); }); // ===== Internet Permissions ===== col.Item().Element(x => x.Background(Colors.Black).Padding(3)) .Text("Internet Permissions").FontColor(Colors.White).Bold(); col.Item().Border(1).Padding(0).Column(cc => { cc.Item().BorderBottom(1).Padding(6).Text($"{Box(false)} Unlimited Internet Access"); cc.Item().BorderBottom(1).Padding(6).Text($"{Box(false)} Limited Internet Access"); cc.Item().BorderBottom(1).Padding(6).Text($"{Box(false)} PSI Instant Messenger"); cc.Item().BorderBottom(1).Padding(6).Text($"{Box(false)} VPN"); cc.Item().PaddingLeft(14).PaddingTop(4).Text("Justification for the Internet Permissions:"); cc.Item().Padding(6).Border(1).Height(20).Padding(4).Text("-"); }); // ===== Shared Permissions ===== col.Item().PaddingTop(6).Element(x => x.Background(Colors.Black).Padding(3)) .Text("Shared Permissions").FontColor(Colors.White).Bold(); col.Item().Border(1).Padding(0).Element(x => SharedPermissionsTable(x, m)); // ===== Copier ===== col.Item().PaddingTop(6).Element(x => x.Border(1).Padding(8)).Column(cc => { cc.Item().Row(rr => { rr.RelativeItem().Row(r1 => { r1.RelativeItem().Text("Copier Scanner"); r1.RelativeItem().Text($"{Box(true)} Black"); r1.RelativeItem().Text($"{Box(false)} Color"); }); rr.RelativeItem().Row(r2 => { r2.RelativeItem().Text("Copier Printing"); r2.RelativeItem().Text($"{Box(true)} Black"); r2.RelativeItem().Text($"{Box(false)} Color"); }); }); }); }); } void SharedPermissionsTable(IContainer c, ItRequestReportModel m) { c.Table(t => { t.ColumnsDefinition(cols => { cols.ConstantColumn(18); // # cols.RelativeColumn(6); // Share cols.RelativeColumn(1); // Read cols.RelativeColumn(1); // Write cols.RelativeColumn(1); // Delete cols.RelativeColumn(1); // Remove }); // header band t.Header(h => { h.Cell().Element(CellHead).Text(""); h.Cell().Element(CellHead).Text("Shared Permissions"); h.Cell().Element(CellHead).Text("Read"); h.Cell().Element(CellHead).Text("Write"); h.Cell().Element(CellHead).Text("Delete"); h.Cell().Element(CellHead).Text("Remove"); }); var rows = m.SharedPerms.Select((sp, i) => new { Index = i + 1, sp.Share, sp.R, sp.W, sp.D, sp.Remove }).ToList(); foreach (var r in rows) { t.Cell().Element(Cell).Text(r.Index.ToString()); t.Cell().Element(Cell).Text(r.Share ?? ""); t.Cell().Element(Cell).Text(Box(r.R)); t.Cell().Element(Cell).Text(Box(r.W)); t.Cell().Element(Cell).Text(Box(r.D)); t.Cell().Element(Cell).Text(Box(r.Remove)); } int start = rows.Count + 1; // no hardcoded row; if no data, start at 1 for (int i = start; i <= 6; i++) { t.Cell().Element(Cell).Text(i.ToString()); t.Cell().Element(Cell).Text(""); t.Cell().Element(Cell).Text(""); t.Cell().Element(Cell).Text(""); t.Cell().Element(Cell).Text(""); t.Cell().Element(Cell).Text(""); } }); } #endregion #region SECTION A • OS Requirements // ===================== SECTION A – Form Arrangement ===================== // ===================== SECTION A – Form Arrangement (BOXED) ===================== void FormArrangementBlock(IContainer c, ItRequestReportModel m) { // tiny helper: draws the vertical borders for each cell so it looks like 5 boxed panels IContainer BoxCell(IContainer x, bool isLast) => x.BorderLeft(1) .BorderRight(isLast ? 1 : 0) // last cell closes the right border .Padding(6) .MinHeight(45); // keep all equal; adjust to taste c.Column(col => { // italic guide line col.Item() .PaddingBottom(4) .Text("Form Arrangement: User → HOD → IT HOD → FINANCE HOD → CEO/CFO/COO") .Italic(); // OUTER frame col.Item().Border(1).Element(frame => { frame.Table(t => { // 5 equal columns t.ColumnsDefinition(cols => { cols.RelativeColumn(1); cols.RelativeColumn(1); cols.RelativeColumn(1); cols.RelativeColumn(1); cols.RelativeColumn(1); }); // local function to render a boxed panel void Panel(int index0to4, string title, string name, string date) { bool last = index0to4 == 4; t.Cell().Element(x => BoxCell(x, last)).Column(cc => { cc.Item().Text(title); // signature line // Name / Date rows cc.Item().PaddingTop(8).Row(r => { r.ConstantItem(40).Text("Name :"); r.RelativeItem().Text(string.IsNullOrWhiteSpace(name) ? "" : name); }); cc.Item().Row(r => { r.ConstantItem(40).Text("Date :"); r.RelativeItem().Text(string.IsNullOrWhiteSpace(date) ? "" : date); }); }); } // build the 5 panels (plug in your resolved names/dates) Panel(0, "Requested by:", m.RequestorName ?? "", F(m.SubmitDate) ?? ""); Panel(1, "Approved by HOD:", m.HodApprovedBy ?? "", F(m.HodSubmitDate) ?? ""); Panel(2, "Approved by Group IT HOD:", m.GitHodApprovedBy ?? "", F(m.GitHodSubmitDate) ?? ""); Panel(3, "Supported by Finance HOD:", m.FinHodApprovedBy ?? "", F(m.FinHodSubmitDate) ?? ""); Panel(4, "Reviewed & Approved by Management:", m.MgmtApprovedBy ?? "", F(m.MgmtSubmitDate) ?? ""); }); }); }); } #endregion #region SECTION A • Software (General, Utility, Custom) & Shared Permissions #endregion #region SECTION A • Copier #endregion #region SECTION A • Approvals void Approvals(IContainer c, ItRequestReportModel m) { c.Column(col => { col.Item().Row(r => { r.RelativeItem().Column(cc => { cc.Item().Text("Requested by:"); cc.Item().PaddingTop(12).Text("Name : MANDATORY"); cc.Item().Row(rr => { rr.ConstantItem(40).Text("Date :"); rr.RelativeItem().Border(1).Height(14).Text(F(m.SubmitDate)); }); }); r.RelativeItem().Column(cc => { cc.Item().Text("Approved by HOD:"); cc.Item().PaddingTop(12).Text("Name :"); cc.Item().Row(rr => { rr.ConstantItem(40).Text("Date :"); rr.RelativeItem().Border(1).Height(16).Text(""); }); }); }); col.Item().Row(r => { r.RelativeItem().Column(cc => { cc.Item().Text("Approved by Group IT HOD:"); cc.Item().PaddingTop(12).Text("Name :"); cc.Item().Row(rr => { rr.ConstantItem(40).Text("Date :"); rr.RelativeItem().Border(1).Height(16).Text(""); }); }); r.RelativeItem().Column(cc => { cc.Item().Text("Supported by Finance HOD:"); cc.Item().PaddingTop(12).Text("Name :"); cc.Item().Row(rr => { rr.ConstantItem(40).Text("Date :"); rr.RelativeItem().Border(1).Height(16).Text(""); }); }); }); col.Item().Row(r => { r.RelativeItem().Column(cc => { cc.Item().Text("Reviewed & Approved by"); cc.Item().Text("Management:"); cc.Item().PaddingTop(12).Text("Name :"); cc.Item().Row(rr => { rr.ConstantItem(40).Text("Date :"); rr.RelativeItem().Border(1).Height(16).Text(""); }); }); r.RelativeItem().Text(""); }); }); } #endregion // ================= SECTION B ================= #region SECTION B • Asset Information // ===================== SECTION B – EXACT TWO-BLOCK, TWO-PANE LAYOUT ===================== void SectionB_TwoBlocks(IContainer c, ItRequestReportModel m) { c.Column(col => { // Title line exactly like screenshot (italic note on same line) col.Item().Text(text => { text.Span("Section B ").Bold(); text.Span("(To be completed by IT Staff only)").Italic(); }); // ===== Block 1: Asset Information (left) + Remarks (right) ===== col.Item().PaddingTop(4).Border(1).Element(block1 => { block1.Table(t => { t.ColumnsDefinition(cols => { cols.RelativeColumn(1); cols.ConstantColumn(1); // skinny divider (we'll draw borders on cells) cols.RelativeColumn(1); }); // Header row with black bands // Left header t.Cell().Element(x => x.Background(Colors.Black).Padding(3).BorderRight(1)) .Text("Asset Information").FontColor(Colors.White).Bold(); // divider (invisible content, keeps structure) t.Cell().BorderLeft(0).BorderRight(0).Text(""); // Right header t.Cell().Element(x => x.Background(Colors.Black).Padding(3)) .Text("Remarks:-").FontColor(Colors.White).Bold(); // Content row (equal height because same table row) // Left: asset info table t.Cell().Element(x => x.BorderTop(1).BorderRight(1).Padding(0)) .Element(x => SectionB_AssetInfoTable(x, m)); // divider t.Cell().Border(0).Text(""); // Right: remarks box t.Cell().Element(x => x.BorderTop(1).Padding(6).MinHeight(108)) .Text(string.IsNullOrWhiteSpace(m.Remarks) ? "" : m.Remarks); }); }); // ===== Block 2: Requestor Acknowledgement (left) + Completed By (right) ===== // ===== Block 2: Requestor Acknowledgement (left) + Completed By (right) ===== col.Item().PaddingTop(6).Border(1).Element(block2 => { block2.Table(t => { t.ColumnsDefinition(cols => { cols.RelativeColumn(1); cols.ConstantColumn(1); // divider cols.RelativeColumn(1); }); // Left pane (no signature line, compact height) t.Cell().Element(x => x.Padding(8).BorderRight(1)).Column(cc => { cc.Item().Text("Requestor Acknowledgement:").Bold(); cc.Item().PaddingTop(4).Row(r => { r.ConstantItem(44).Text("Name:"); r.RelativeItem().Text(m.RequestorName ?? ""); r.ConstantItem(44).Text("Date:"); r.RelativeItem().Text(F(m.RequestorAcceptedAt)); }); }); // divider t.Cell().Border(0).Text(""); // Right pane (no signature line) t.Cell().Element(x => x.Padding(8)).Column(cc => { cc.Item().Text("Completed by:").Bold(); cc.Item().PaddingTop(4).Row(r => { r.ConstantItem(44).Text("Name:"); r.RelativeItem().Text(m.ItCompletedBy ?? ""); r.ConstantItem(44).Text("Date:"); r.RelativeItem().Text(F(m.ItAcceptedAt)); }); }); }); }); }); } // Left-pane table used in Block 1 (matches your rows & borders) void SectionB_AssetInfoTable(IContainer c, ItRequestReportModel m) { c.Table(t => { t.ColumnsDefinition(cols => { cols.RelativeColumn(2); // label cols.RelativeColumn(2); // value box }); void Row(string label, string value) { t.Cell().BorderBottom(1).Padding(6).Text(label); t.Cell().BorderLeft(1).BorderBottom(1).Padding(3).Text(value ?? ""); } // top row gets its own bottom borders; left cell also has right divider Row("Asset No:", m.AssetNo); Row("Machine ID:", m.MachineId); Row("IP Add:", m.IpAddress); Row("Wired Mac Add:", m.WiredMac); Row("Wi-Fi Mac Add:", m.WifiMac); Row("Dial-up Acc:", m.DialupAcc); }); } #endregion #region SECTION B • Requestor Acknowledgement / Completed By #endregion // Naive text wrap for justification box #region HELPER • JointLines #endregion } }