Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introdução

A Classe NfsReport foi criada com o objetivo de abstrair a crição de Planilhas Eletrônicas e manter as práticas correntes desse processo no NFS.

Criar e Manipular Planilha

/**
 * Cria objeto NfsReport permintindo o preenchimento e formatação.
 *
 * @param array $options Opções da planilha
 * $options = [
 *		'title' => ?string = 'título do documento'
 *		'subject' => ?string = 'assunto do documento'
 *		'sheetName' => ?string = 'nome da planilha'
 *		'userName' => ?string = 'nome do usuário'
 * ];
 */
public static function createSpreadsheet(array $options): self
/**
 * Cria uma nova aba em uma Planilha.
 *
 * @param string $sheetName Nome da aba / worksheet
 * @param int    $index     Índice da aba / worksheet
 */
public function createSheet(string $sheetName, int $index = 0): self
/**
 * Define planilha ativa por índice.
 *
 * @param int $index Índice da planilha (começa no 0)
 */
public function setActiveSheetByIndex(int $index): self
/**
 * Define planilha ativa pelo nome da planilha.
 *
 * @param string $name Nome da planilha
 */
public function setActiveSheetByName(string $name): self
/**
 * Define o título da planilha.
 *
 * @param string $title Título da planilha
 */
public function setTitle(string $title): self
/**
 * Finaliza objeto e libera recursos alocados.
 */
public function close()

Manipulação de Dados

/**
 * Define valor de uma célula.
 *
 * @param string $cell     Referência da Célula [A1|..|ZN]
 * @param mixed  $value    Valor da célula
 * @param string $dataType Tipo de dado (Referência Constantes Tipos de Dado)
 */
public function setCellValue(string $cell, mixed $value, ?string $dataType = null): self
/**
 * Preenche planilha com valores do array.
 *
 * @param array  $rows Dados a adicionar
 * @param string $cell Célula de referência para preenchimento
 */
public function addFromArray(array $rows, string $cell = 'A1'): self
/**
 * Mescla celulas e preenche.
 *
 * @param string $reference Referência para mesclagem e preenchimento
 * @param mixed  $value     Valor de preenchimento da célula mesclada
 * @param mixed  $wrapText  Se o valor aceita quebra de linha ou não
 */
public function mergeCells(string $reference, mixed $value = null, bool $wrapText = true): self
/**
 * Define explicitamento o tipo de dados de uma célula.
 *
 * @param string $cell     Referência da célula
 * @param string $dataType $dataType Tipo de dado
 */
public function setCellDataType(string $cell, string $dataType): self
/**
 * Define a formatação de campos/bloco de células.
 * IMPORTANTE: usar a string do formato, não as constantes definidas na classe.
 *
 * @param string $reference Referência da célula ('A1'..'Z9'|) ou bloco ('A1:C3'..'A1:Z5')
 * @param string $format    String do formato (Referência Constantes Tipos de Dado)
 */
public function setNumberFormat(string $reference, string $format): self
    /**
     * Insere imagem vinculada a uma célula.
     *
     * @param string     $base64    Base64 da imagem que será inserida
     * @param string     $reference Referência da célula
     * @param null|array $props     Propriedades da imagem
     *                              [height, width, name, description, offSetX, offSetY, rotation]
     */
    public function addImageFromBase64(string $base64, string $reference, ?array $props = []): self

Formatação de Dados

/**
 * Define largura da coluna.
 *
 * @param string $col   Referência da coluna (A..Z..AA..ZZ)
 * @param float  $width Largura relativa ao tamanho da fonte
 */
public function setColWidth(string $col, float $width): self
 /**
 * Define altura da linha.
 *
 * @param int   $row    Referência da linha
 * @param float $height Altura da linha relativa ao tamanho da fonte
 */
public function setRowHeight(int $row, float $height): self
/**
 * Define referência para aplicação de propriedades.
 *
 * @param string $reference Célula ou bloco de células
 */
public function getStyle(string $reference): self
/**
 * Aplica estilos usando array de propriedades.
 * IMPORTANTE: usar a string do formato, não as constantes definidas na classe.
 * Usar as strings definidas em cada propriedade (de acordo com os métodos dessa classe).
 *
 * @param array $props Propriedades definidas (Referência Externa Estilos)
 */
public function applyFromArray(array $props): self
/**
 * Define o alinhamento da célula ou celulas.
 *
 * @param string $reference Referência para mesclagem e preenchimento
 * @param string $horAlign  Alinhamento horizontal (Referência Constantes Alinhamento Horizontal)
 * @param string $vertAlign Alinhamento vertical (Referência Constantes Alinhamento Vertical)
 * @param bool   $wrapText  Se o valor aceita quebra de linha ou não
 */
public function setAlignment($reference, $horAlign = 'general', $vertAlign = 'v-top', $wrapText = false): self
/**
 * Define formatação da célula ou células.
 *
 * @param string $reference  Referência para mesclagem e preenchimento
 * @param float  $fontSize   Tamanho da fonte
 * @param string $fontColor  Cor da fonte
 * @param bool   $fontBold   Se o texto estará em negrito ou não
 * @param bool   $fontItalic Se o texto estará em itálico ou não
 * @param string $bgColor    Cor de fundo / background
 */
public function setStyle(string $reference, float $fontSize = 11, ?string $fontColor = null, bool $fontBold = false, bool $fontItalic = false, ?string $bgColor = null): self
/**
 * Tenta determinar a largura de uma coluna
 * Se o parâmetro não for especificado, define autosize para todas as colunas preenchidas.
 *
 * @param string $col Referência da coluna (A-Z..AA..ZZ)
 */
public function autoSize(?string $col = null): self
/**
 * Define célula/bloco como.
 *
 * @param string $reference Referência célula/bloco
 * @param bool   $prop      Se o valor aceita quebra de linha ou não
 */
public function setWrapText(string $reference, bool $prop = true): self

Quando se exporta o crud list num excel os campos LOVN são exibidos juntos, para facilitar a quebra de linha foi criado o parâmetro CRUD_LIST_LOVN_LINE_BREAK para fazer o parse no excel.

CRUD_LIST_LOVN_LINE_BREAK

Referências

Constantes Definidas

Tipo de Preenchimento de Cor de Fundo / Background

Opções: 'none', 'solid', 'linear'

Alinhamento

Opções Horizontal: 'general', 'left', 'right', 'center', 'justify' Opções Vertical: 'v-top', 'v-center', 'v-bottom', 'v-justify'

Tipos de Dado

Opções: 'string', 'formula', 'numeric', 'boolean', 'null', 'inline', 'error'

Tipos de Borda

Opções: 'none', 'dashDot', 'dashDotDot', 'dashed', 'dotted', 'double', 'hair', 'medium', 'mediumDashDot', 'mediumDashDotDot', 'mediumDashed', 'slantDashDot', 'thick', 'thin'

Fontes Sublinhadas / Underline

Opções: 'none', 'double', 'single'

Referências Externas

IMPORTANTE: nos relatórios gerados através da tabela nfs_reports o nome do objeto criado pelo processo foi definido como $sheet (na versão antiga era $objPHPExcel). {.is-warning}

Casos de Uso

Criação e Preenchimento

$report = NfsReport::createSpreadsheet([
	'title' => 'Planilha',
	'subject' => 'Teste de classe NfsReport',
	'userName' => 'Teste Unitário',
	'sheetName' => 'Planilha Teste',
]);

$report->addFromArray([
	[1, 2, 3, 4, 5],
	[6, 7, 8, 9, 10],
	[11, 12, 13, 14, 15],
	[16, 17, 18, 19, 20],
], 'A1');

$report->CreateSheet('Planilha 2', 1);
$report->addFromArray([
	['Core', 'Dev', 'Support'],
	['Simova', 'NFS', 'Report'],
], 'A1');

$report->setActiveSheetByIndex(0);
$report->addFromArray([
	['Core', 'Dev', 'Support'],
	['Simova', 'NFS', 'Report'],
], 'A5');

Criação, Preenchimento e Formatação

$report = NfsReport::createSpreadsheet([
	'title' => 'Planilha',
	'subject' => 'Teste de classe NfsReport',
	'userName' => 'Teste Unitário',
]);

$report->addFromArray([
	[1, 2, 3, 4, 5],
	[6, 7, 8, 9, 10],
	[11, 12, 13, 14, 15],
	[16, 17, 18, 19, 20],
], 'A1');

$report->getStyle('A1:E1')
	->applyFromArray([
  	'fill' => [
			'fillType' => 'solid',
      'color' => [
      	'rgb' => 'FF0000',
			],
		],
		'font' => [
    	'name' => 'Calibri',
			'bold' => true,
			'underline' => 'double',
			'color' => [
				'rgb' => 'FFFFFF',
			],
			'size' => 14.5,
		],
		'borders' => [
			'allBorders' => [
				'borderStyle' => 'double',
				'color' => [
					'argb' => '000000',
				],
			],
		],
		'alignment' => [
			'horizontal' => 'center',
			'vertical' => 'v-center',
			'wrapText' => true,
		],
		'quotePrefix' => true,
	]);

$report->getStyle('A2:E2')
	->applyFromArray([
		'fill' => [
			'fillType' => 'solid',
			'color' => [
				'rgb' => '00FF83',
			],
		],
		'font' => [
			'name' => 'Arial',
			'bold' => true,
			'italic' => true,
			'superscript' => true,
			'underline' => 'single',
			'strikethrough' => true,
			'color' => [
				'rgb' => '808080',
			],
			'size' => 11,
		],
		'borders' => [
			'allBorders' => [
				'borderStyle' => 'dashDot',
				'color' => [
					'rgb' => 'f9a11a',
				],
			],
		],
		'alignment' => [
			'horizontal' => 'center',
			'vertical' => 'v-center',
			'wrapText' => true,
		],
		'quotePrefix' => true,
	]);

$report->getStyle('A3:E4')
	->applyFromArray([
		'font' => [
			'name' => 'Tahoma',
			'superscript' => true,
			'color' => [
				'rgb' => 'FF000000',
			],
			'size' => 8,
		],
	]);

Outro Exemplo

$display_name = 'MSI';
$sheet = NfsReport::createSpreadsheet([
  'title' => $display_name,
  'subject' => $display_name,
  'sheetName' => $display_name
]);
$row = 1;

$styleTitleCenter = [
	'font' => [
		'size' => 12,
		'bold' => true,
		'color' => ['rgb' => '000000']
	],
	'fill' => [
		'fillType' => 'solid',
		'color' => ['rgb' => 'FFFFFF']
	],
	'alignment' => [
		'horizontal' => 'center',
        'vertical' => 'v-center'
	],
	'borders' => [
		'allBorders' => [
			'borderStyle' => 'thin',
			'color' => ['rgb' => 'FFFFFF']
		]
	]
];

$styleTitleLeft = [
	'font' => [
		'size' => 12,
		'bold' => true,
		'color' => ['rgb' => '000000']
	],
	'fill' => [
		'fillType' => 'solid',
		'color' => ['rgb' => 'FFFFFF']
	],
	'alignment' => [
		'horizontal' => 'left',
        'vertical' => 'v-center'
	],
	'borders' => [
		'allBorders' => [
			'borderStyle' => 'thin',
			'color' => ['rgb' => 'FFFFFF']
		]
	]
];

$styleTitleRight = [
	'font' => [
		'size' => 12,
		'bold' => true,
		'color' => ['rgb' => '000000']
	],
	'fill' => [
		'fillType' => 'solid',
		'color' => ['rgb' => 'FFFFFF']
	],
	'alignment' => [
		'horizontal' => 'right',
        'vertical' => 'v-center'
	],
	'borders' => [
		'allBorders' => [
			'borderStyle' => 'thin',
			'color' => ['rgb' => 'FFFFFF']
		]
	]
];

$styleHeadCenterBlue = [
	'font' => [
		'size' => 11,
		'bold' => true,
		'color' => ['rgb' => 'FFFFFF']
	],
	'fill' => [
		'fillType' => 'solid',
		'color' => ['rgb' => '002060']
	],
	'alignment' => [
		'horizontal' => 'center',
        'vertical' => 'v-center'
	],
	'borders' => [
		'allBorders' => [
			'borderStyle' => 'thin',
			'color' => ['rgb' => '002060']
		]
	]
];

$styleHeadLeftBlue = [
	'font' => [
		'size' => 11,
		'bold' => true,
		'color' => ['rgb' => 'FFFFFF']
	],
	'fill' => [
		'fillType' => 'solid',
		'color' => ['rgb' => '002060']
	],
	'alignment' => [
		'horizontal' => 'left',
        'vertical' => 'v-center'
	],
	'borders' => [
		'allBorders' => [
			'borderStyle' => 'thin',
			'color' => ['rgb' => '002060']
		]
	]
];

$styleHeadRightBlue = [
	'font' => [
		'size' => 11,
		'bold' => true,
		'color' => ['rgb' => 'FFFFFF']
	],
	'fill' => [
		'fillType' => 'solid',
		'color' => ['rgb' => '002060']
	],
	'alignment' => [
		'horizontal' => 'right',
        'vertical' => 'v-center'
	],
	'borders' => [
		'allBorders' => [
			'borderStyle' => 'thin',
			'color' => ['rgb' => '002060']
		]
	]
];

$styleHeadCenterPurple = [
	'font' => [
		'size' => 11,
		'bold' => true,
		'color' => ['rgb' => 'FFFFFF']
	],
	'fill' => [
		'fillType' => 'solid',
		'color' => ['rgb' => '421c5e']
	],
	'alignment' => [
		'horizontal' => 'center',
        'vertical' => 'v-center'
	],
	'borders' => [
		'allBorders' => [
			'borderStyle' => 'thin',
			'color' => ['rgb' => '421c5e']
		]
	]
];

$styleHeadLeftPurple = [
	'font' => [
		'size' => 11,
		'bold' => true,
		'color' => ['rgb' => 'FFFFFF']
	],
	'fill' => [
		'fillType' => 'solid',
		'color' => ['rgb' => '421c5e']
	],
	'alignment' => [
		'horizontal' => 'left',
        'vertical' => 'v-center'
	],
	'borders' => [
		'allBorders' => [
			'borderStyle' => 'thin',
			'color' => ['rgb' => '421c5e']
		]
	]
];

$styleHeadRightPurple = [
	'font' => [
		'size' => 11,
		'bold' => true,
		'color' => ['rgb' => 'FFFFFF']
	],
	'fill' => [
		'fillType' => 'solid',
		'color' => ['rgb' => '421c5e']
	],
	'alignment' => [
		'horizontal' => 'right',
        'vertical' => 'v-center'
	],
	'borders' => [
		'allBorders' => [
			'borderStyle' => 'thin',
			'color' => ['rgb' => '421c5e']
		]
	]
];

$styleCenterWhite = [
	'font' => [
		'size' => 11,
		'bold' => false,
		'color' => ['rgb' => '000000']
	],
	'fill' => [
		'fillType' => 'solid',
		'color' => ['rgb' => 'FFFFFF']
	],
	'alignment' => [
		'horizontal' => 'center',
        'vertical' => 'v-center'
	],
	'borders' => [
		'allBorders' => [
			'borderStyle' => 'thin',
			'color' => ['rgb' => 'FFFFFF']
		]
	]
];

$styleLeftWhite = [
	'font' => [
		'size' => 11,
		'bold' => false,
		'color' => ['rgb' => '000000']
	],
	'fill' => [
		'fillType' => 'solid',
		'color' => ['rgb' => 'FFFFFF']
	],
	'alignment' => [
		'horizontal' => 'left',
        'vertical' => 'v-center'
	],
	'borders' => [
		'allBorders' => [
			'borderStyle' => 'thin',
			'color' => ['rgb' => 'FFFFFF']
		]
	]
];

$styleRightWhite = [
	'font' => [
		'size' => 11,
		'bold' => false,
		'color' => ['rgb' => '000000']
	],
	'fill' => [
		'fillType' => 'solid',
		'color' => ['rgb' => 'FFFFFF']
	],
	'alignment' => [
		'horizontal' => 'right',
        'vertical' => 'v-center'
	],
	'borders' => [
		'allBorders' => [
			'borderStyle' => 'thin',
			'color' => ['rgb' => 'FFFFFF']
		]
	]
];

$styleCenterBlue = [
	'font' => [
		'size' => 11,
		'bold' => false,
		'color' => ['rgb' => '000000']
	],
	'fill' => [
		'fillType' => 'solid',
		'color' => ['rgb' => 'dae3f3']
	],
	'alignment' => [
		'horizontal' => 'center',
        'vertical' => 'v-center'
	],
	'borders' => [
		'allBorders' => [
			'borderStyle' => 'thin',
			'color' => ['rgb' => 'dae3f3']
		]
	]
];

$styleLeftBlue = [
	'font' => [
		'size' => 11,
		'bold' => false,
		'color' => ['rgb' => '000000']
	],
	'fill' => [
		'fillType' => 'solid',
		'color' => ['rgb' => 'dae3f3']
	],
	'alignment' => [
		'horizontal' => 'left',
        'vertical' => 'v-center'
	],
	'borders' => [
		'allBorders' => [
			'borderStyle' => 'thin',
			'color' => ['rgb' => 'dae3f3']
		]
	]
];

$styleRightBlue = [
	'font' => [
		'size' => 11,
		'bold' => false,
		'color' => ['rgb' => '000000']
	],
	'fill' => [
		'fillType' => 'solid',
		'color' => ['rgb' => 'dae3f3']
	],
	'alignment' => [
		'horizontal' => 'right',
        'vertical' => 'v-center'
	],
	'borders' => [
		'allBorders' => [
			'borderStyle' => 'thin',
			'color' => ['rgb' => 'dae3f3']
		]
	]
];


$styleLightBlue = [
	'font' => [
		'size' => 11,
		'bold' => false,
		'color' => ['rgb' => '000000']
	],
	'fill' => [
		'fillType' => 'solid',
		'color' => ['rgb' => 'dae3f3']
	],
	'alignment' => [
		'horizontal' => 'right',
        'vertical' => 'v-center'
	],
	'borders' => [
		'allBorders' => [
			'borderStyle' => 'thin',
			'color' => ['rgb' => 'dae3f3']
		]
	]
];

$styleDarkBlue = [
	'font' => [
		'size' => 11,
		'bold' => false,
		'color' => ['rgb' => '000000']
	],
	'fill' => [
		'fillType' => 'solid',
		'color' => ['rgb' => 'b4c7e7']
	],
	'alignment' => [
		'horizontal' => 'right',
        'vertical' => 'v-center'
	],
	'borders' => [
		'allBorders' => [
			'borderStyle' => 'thin',
			'color' => ['rgb' => 'b4c7e7']
		]
	]
];


$styleCenterPurple = [
	'font' => [
		'size' => 11,
		'bold' => false,
		'color' => ['rgb' => '000000']
	],
	'fill' => [
		'fillType' => 'solid',
		'color' => ['rgb' => 'ccccff']
	],
	'alignment' => [
		'horizontal' => 'center',
        'vertical' => 'v-center'
	],
	'borders' => [
		'allBorders' => [
			'borderStyle' => 'thin',
			'color' => ['rgb' => 'ccccff']
		]
	]
];

$styleLeftPurple = [
	'font' => [
		'size' => 11,
		'bold' => false,
		'color' => ['rgb' => '000000']
	],
	'fill' => [
		'fillType' => 'solid',
		'color' => ['rgb' => 'ccccff']
	],
	'alignment' => [
		'horizontal' => 'left',
        'vertical' => 'v-center'
	],
	'borders' => [
		'allBorders' => [
			'borderStyle' => 'thin',
			'color' => ['rgb' => 'ccccff']
		]
	]
];

$styleRightPurple = [
	'font' => [
		'size' => 11,
		'bold' => false,
		'color' => ['rgb' => '000000']
	],
	'fill' => [
		'fillType' => 'solid',
		'color' => ['rgb' => 'ccccff']
	],
	'alignment' => [
		'horizontal' => 'right',
        'vertical' => 'v-center'
	],
	'borders' => [
		'allBorders' => [
			'borderStyle' => 'thin',
			'color' => ['rgb' => 'ccccff']
		]
	]
];


$styleLightPurple = [
	'font' => [
		'size' => 11,
		'bold' => false,
		'color' => ['rgb' => '000000']
	],
	'fill' => [
		'fillType' => 'solid',
		'color' => ['rgb' => 'ccccff']
	],
	'alignment' => [
		'horizontal' => 'right',
        'vertical' => 'v-center'
	],
	'borders' => [
		'allBorders' => [
			'borderStyle' => 'thin',
			'color' => ['rgb' => 'ccccff']
		]
	]
];

$styleDarkPurple = [
	'font' => [
		'size' => 11,
		'bold' => false,
		'color' => ['rgb' => '000000']
	],
	'fill' => [
		'fillType' => 'solid',
		'color' => ['rgb' => '9b9bff']
	],
	'alignment' => [
		'horizontal' => 'right',
        'vertical' => 'v-center'
	],
	'borders' => [
		'allBorders' => [
			'borderStyle' => 'thin',
			'color' => ['rgb' => '9b9bff']
		]
	]
];

foreach (range('A','X') as $column) {
	$sheet->getColumnDimension($column)->setAutoSize(true);
}
foreach ($templateData['projeto_list'] as $s) {
	$sheet->getStyle("A{$row}")->applyFromArray($styleTitleLeft);
	$sheet->getStyle("B{$row}:I{$row}")->applyFromArray($styleTitleCenter);
	$sheet->SetCellValue("A{$row}", 'DISCIPLINA');
	$sheet->SetCellValue("B{$row}", 'PROJETO');
	$sheet->SetCellValue("C{$row}", 'ÁREA');
	$sheet->SetCellValue("D{$row}", 'SEMANA');
	$sheet->SetCellValue("E{$row}", 'INFORMAÇÃO');
	$sheet->SetCellValue("F{$row}", 'PROGRAMAÇÃO');
	$sheet->SetCellValue("G{$row}", 'MEDIÇÃO');
	$sheet->SetCellValue("H{$row}", 'Cód');
	$sheet->SetCellValue("I{$row}", 'Unid.');
	$row ++;
	foreach ($templateData['processo_list'][$s['CHAVE']] as $p) {
		$tipo_processo = $p['TIPO_PROCESSO'];
		
		$styleHeadCenter = $styleHeadCenterBlue;
		$styleHeadLeft = $styleHeadLeftBlue;
		$styleHeadRight = $styleHeadRightBlue;
		if ($tipo_processo == '1') {
			$styleHeadCenter = $styleHeadCenterBlue;
			$styleHeadLeft = $styleHeadLeftBlue;
			$styleHeadRight = $styleHeadRightBlue;
		} else {
			$styleHeadCenter = $styleHeadCenterPurple;
			$styleHeadLeft = $styleHeadLeftPurple;
			$styleHeadRight = $styleHeadRightPurple;	
		}

		$sheet->getStyle("A{$row}:I{$row}")->applyFromArray($styleHeadCenter);
		$sheet->getStyle("A{$row}")->applyFromArray($styleHeadLeft);
		$sheet->SetCellValue("A{$row}", $p['DISCIPLINA']);
		$sheet->SetCellValue("B{$row}", $p['PROJETO']);
		$sheet->getStyle("C{$row}")->applyFromArray($styleHeadLeft);
		$sheet->SetCellValue("C{$row}", $p['AREA']);
		$sheet->SetCellValue("D{$row}", $p['SEMANA']);
		$sheet->getStyle("E{$row}")->applyFromArray($styleHeadLeft);
		$sheet->SetCellValue("E{$row}", $p['INFORMACAO']);
		$sheet->getStyle("F{$row}:G{$row}")->applyFromArray($styleHeadRight);
		$sheet->SetCellValue("F{$row}", $p['PROGRAMACAO']);
		$sheet->SetCellValue("G{$row}", $p['MEDICAO']);
		$sheet->SetCellValue("H{$row}", $p['CODIGO']);
		$sheet->SetCellValue("I{$row}", $p['UNIDADE']);
		$row++;
		
		foreach ($templateData['modulo_list'][$p['CHAVE']] as $m) {
			$styleCenter = $styleCenterWhite;
			$styleLeft = $styleLeftWhite;
			$styleRight = $styleLightBlue;
			if ($tipo_processo == '1') {
				if (($row%2) == 1) {
					$styleCenter = $styleCenterWhite;
					$styleLeft = $styleLeftWhite;
					$styleRight = $styleLightBlue;
				} else {
					$styleCenter = $styleCenterBlue;
					$styleLeft = $styleLeftBlue;
					$styleRight = $styleDarkBlue;
				}
			} else {
				if (($row%2) == 1) {
					$styleCenter = $styleCenterWhite;
					$styleLeft = $styleLeftWhite;
					$styleRight = $styleLightBlue;
				} else {
					$styleCenter = $styleCenterPurple;
					$styleLeft = $styleLeftPurple;
					$styleRight = $styleDarkPurple;
				}
			}
			$sheet->getStyle("A{$row}:I{$row}")->applyFromArray($styleCenter);
			$sheet->getStyle("A{$row}")->applyFromArray($styleLeft);
			$sheet->SetCellValue("A{$row}", $m['DISCIPLINA']);
			$sheet->SetCellValue("B{$row}", $m['PROJETO']);
			$sheet->getStyle("C{$row}")->applyFromArray($styleLeft);
			$sheet->SetCellValue("C{$row}", $m['AREA']);
			$sheet->SetCellValue("D{$row}", $m['SEMANA']);
			$sheet->getStyle("E{$row}")->applyFromArray($styleLeft);
			$sheet->SetCellValue("E{$row}", $m['INFORMACAO']);
			$sheet->getStyle("F{$row}:G{$row}")->applyFromArray($styleRight);
			$sheet->SetCellValue("F{$row}", $m['PROGRAMACAO']);
			$sheet->SetCellValue("G{$row}", $m['MEDICAO']);
			$sheet->SetCellValue("H{$row}", $m['CODIGO']);
			$sheet->SetCellValue("I{$row}", $m['UNIDADE']);
			$row++;
		}
		$row++;
	}
	$row++;
}

Exemplo de Planilha com Multiplas Abas

// IMPORTANT: The variable $sheet is required to the report works!

$sheet = NfsReport::createSpreadsheet([
    'title'     => 'Relatorio Horas dos Tecnicos',
    'subject'   => 'Horas dos Técnicos',
    'sheetName' => 'Resumo Geral',
]);

// ── Styles ────────────────────────────────────────────────────────────────────

$styleTitle = [
    'font'      => ['size' => 24, 'bold' => true, 'color' => ['rgb' => '000000']],
    'fill'      => ['fillType' => 'solid', 'color' => ['rgb' => 'E5E5E5']],
    'alignment' => ['horizontal' => 'center', 'vertical' => 'center'],
    'borders'   => ['allBorders' => ['borderStyle' => 'thin', 'color' => ['rgb' => '000000']]],
];

$styleTitleCenter = [
    'font'      => ['size' => 12, 'bold' => true, 'color' => ['rgb' => '000000']],
    'fill'      => ['fillType' => 'solid', 'color' => ['rgb' => 'FFFFFF']],
    'alignment' => ['horizontal' => 'center', 'vertical' => 'center'],
    'borders'   => ['allBorders' => ['borderStyle' => 'thin', 'color' => ['rgb' => '000000']]],
];

$styleTitleCenterGray = [
    'font'      => ['size' => 12, 'bold' => true, 'color' => ['rgb' => '000000']],
    'fill'      => ['fillType' => 'solid', 'color' => ['rgb' => 'DCDCDC']],
    'alignment' => ['horizontal' => 'center', 'vertical' => 'center'],
    'borders'   => ['allBorders' => ['borderStyle' => 'thin', 'color' => ['rgb' => '000000']]],
];

$styleCenterWhite = [
    'font'      => ['size' => 11, 'bold' => false, 'color' => ['rgb' => '000000']],
    'fill'      => ['fillType' => 'solid', 'color' => ['rgb' => 'FFFFFF']],
    'alignment' => ['horizontal' => 'center', 'vertical' => 'center'],
    'borders'   => ['allBorders' => ['borderStyle' => 'thin', 'color' => ['rgb' => '000000']]],
];

$styleCenterRed = [
    'font'      => ['size' => 11, 'bold' => false, 'color' => ['rgb' => '000000']],
    'fill'      => ['fillType' => 'solid', 'color' => ['rgb' => 'FF9999']],
    'alignment' => ['horizontal' => 'center', 'vertical' => 'center'],
    'borders'   => ['allBorders' => ['borderStyle' => 'thin', 'color' => ['rgb' => '000000']]],
];

$styleCenterYellow = [
    'font'      => ['size' => 11, 'bold' => true, 'color' => ['rgb' => '000000']],
    'fill'      => ['fillType' => 'solid', 'color' => ['rgb' => 'FFFF99']],
    'alignment' => ['horizontal' => 'center', 'vertical' => 'center'],
    'borders'   => ['allBorders' => ['borderStyle' => 'thin', 'color' => ['rgb' => '000000']]],
];

$styleCenterGray = [
    'font'      => ['size' => 11, 'bold' => false, 'color' => ['rgb' => '000000']],
    'fill'      => ['fillType' => 'solid', 'color' => ['rgb' => 'DCDCDC']],
    'alignment' => ['horizontal' => 'center', 'vertical' => 'center'],
    'borders'   => ['allBorders' => ['borderStyle' => 'thin', 'color' => ['rgb' => '000000']]],
];

// ── Fake data per sheet ───────────────────────────────────────────────────────

$sheets = [
    [
        'name'  => 'Resumo Geral',
        'title' => 'CONTROLE DE TEMPO DE CHAMADO X ATENDIMENTO — GERAL',
        'dados_list' => [
            ['DATA_CHAMADO' => '2024-03-01 08:15', 'FLAG_MAQUINA_PARADA' => 'SIM',  'NUMERO_OS' => 'OS-1001', 'TIPO_OS' => 'Corretiva',  'STATUS_OS' => 'Concluída',    'CLIENTE' => 'Agro São Paulo',     'DATA_ATENDIMENTO' => '2024-03-01 09:45', 'TEMPO_ATENDIMENTO' => 1.50, 'DATA_CONCLUSAO' => '2024-03-01 14:00', 'TEMPO_CONCLUSAO' => 4.25,  'TECNICO' => 'Carlos Silva'],
            ['DATA_CHAMADO' => '2024-03-02 10:00', 'FLAG_MAQUINA_PARADA' => 'NÃO', 'NUMERO_OS' => 'OS-1002', 'TIPO_OS' => 'Preventiva', 'STATUS_OS' => 'Concluída',    'CLIENTE' => 'Fazenda Boa Vista', 'DATA_ATENDIMENTO' => '2024-03-02 12:30', 'TEMPO_ATENDIMENTO' => 2.50, 'DATA_CONCLUSAO' => '2024-03-02 18:00', 'TEMPO_CONCLUSAO' => 5.50,  'TECNICO' => 'Marcos Oliveira'],
            ['DATA_CHAMADO' => '2024-03-03 07:30', 'FLAG_MAQUINA_PARADA' => 'SIM',  'NUMERO_OS' => 'OS-1003', 'TIPO_OS' => 'Corretiva',  'STATUS_OS' => 'Em andamento', 'CLIENTE' => 'Transportes Rápido','DATA_ATENDIMENTO' => '2024-03-03 11:00', 'TEMPO_ATENDIMENTO' => 3.50, 'DATA_CONCLUSAO' => '2024-03-03 17:30', 'TEMPO_CONCLUSAO' => 32.00, 'TECNICO' => 'Ana Ferreira'],
            ['DATA_CHAMADO' => '2024-03-04 09:00', 'FLAG_MAQUINA_PARADA' => 'NÃO', 'NUMERO_OS' => 'OS-1004', 'TIPO_OS' => 'Revisão',    'STATUS_OS' => 'Concluída',    'CLIENTE' => 'Mineradora Delta',  'DATA_ATENDIMENTO' => '2024-03-04 10:00', 'TEMPO_ATENDIMENTO' => 1.00, 'DATA_CONCLUSAO' => '2024-03-04 13:00', 'TEMPO_CONCLUSAO' => 3.00,  'TECNICO' => 'João Pereira'],
            ['DATA_CHAMADO' => '2024-03-05 13:00', 'FLAG_MAQUINA_PARADA' => 'SIM',  'NUMERO_OS' => 'OS-1005', 'TIPO_OS' => 'Corretiva',  'STATUS_OS' => 'Concluída',    'CLIENTE' => 'Logística Sul',     'DATA_ATENDIMENTO' => '2024-03-05 16:30', 'TEMPO_ATENDIMENTO' => 3.50, 'DATA_CONCLUSAO' => '2024-03-05 20:00', 'TEMPO_CONCLUSAO' => 35.00, 'TECNICO' => 'Paula Ramos'],
        ],
        'media_atendimento'  => 2.40,
        'media_conclusao'    => 15.95,
        'dados_tecnico_list' => [
            ['TECNICO' => 'Carlos Silva',    'MEDIA_ATENDIMENTO' => 1.50, 'MEDIA_CONCLUSAO' => 4.25,  'TEMPO_ATENDIMENTO' => 1.50, 'QTD_ATENDIMENTO' => 1, 'TEMPO_CONCLUSAO' => 4.25],
            ['TECNICO' => 'Marcos Oliveira', 'MEDIA_ATENDIMENTO' => 2.50, 'MEDIA_CONCLUSAO' => 5.50,  'TEMPO_ATENDIMENTO' => 2.50, 'QTD_ATENDIMENTO' => 1, 'TEMPO_CONCLUSAO' => 5.50],
            ['TECNICO' => 'Ana Ferreira',    'MEDIA_ATENDIMENTO' => 3.50, 'MEDIA_CONCLUSAO' => 32.00, 'TEMPO_ATENDIMENTO' => 3.50, 'QTD_ATENDIMENTO' => 1, 'TEMPO_CONCLUSAO' => 32.00],
            ['TECNICO' => 'João Pereira',    'MEDIA_ATENDIMENTO' => 1.00, 'MEDIA_CONCLUSAO' => 3.00,  'TEMPO_ATENDIMENTO' => 1.00, 'QTD_ATENDIMENTO' => 1, 'TEMPO_CONCLUSAO' => 3.00],
            ['TECNICO' => 'Paula Ramos',     'MEDIA_ATENDIMENTO' => 3.50, 'MEDIA_CONCLUSAO' => 35.00, 'TEMPO_ATENDIMENTO' => 3.50, 'QTD_ATENDIMENTO' => 1, 'TEMPO_CONCLUSAO' => 35.00],
        ],
    ],
    [
        'name'  => 'Carlos Silva',
        'title' => 'CONTROLE DE TEMPO DE CHAMADO X ATENDIMENTO — CARLOS SILVA',
        'dados_list' => [
            ['DATA_CHAMADO' => '2024-03-01 08:15', 'FLAG_MAQUINA_PARADA' => 'SIM',  'NUMERO_OS' => 'OS-1001', 'TIPO_OS' => 'Corretiva',  'STATUS_OS' => 'Concluída',    'CLIENTE' => 'Agro São Paulo',    'DATA_ATENDIMENTO' => '2024-03-01 09:45', 'TEMPO_ATENDIMENTO' => 1.50, 'DATA_CONCLUSAO' => '2024-03-01 14:00', 'TEMPO_CONCLUSAO' => 4.25,  'TECNICO' => 'Carlos Silva'],
            ['DATA_CHAMADO' => '2024-03-06 08:00', 'FLAG_MAQUINA_PARADA' => 'NÃO', 'NUMERO_OS' => 'OS-1006', 'TIPO_OS' => 'Preventiva', 'STATUS_OS' => 'Concluída',    'CLIENTE' => 'Frigorífico Norte', 'DATA_ATENDIMENTO' => '2024-03-06 10:30', 'TEMPO_ATENDIMENTO' => 2.50, 'DATA_CONCLUSAO' => '2024-03-06 15:00', 'TEMPO_CONCLUSAO' => 4.50,  'TECNICO' => 'Carlos Silva'],
            ['DATA_CHAMADO' => '2024-03-07 14:00', 'FLAG_MAQUINA_PARADA' => 'SIM',  'NUMERO_OS' => 'OS-1007', 'TIPO_OS' => 'Corretiva',  'STATUS_OS' => 'Em andamento', 'CLIENTE' => 'Construtora ABC',   'DATA_ATENDIMENTO' => '2024-03-07 17:00', 'TEMPO_ATENDIMENTO' => 3.00, 'DATA_CONCLUSAO' => '2024-03-07 22:00', 'TEMPO_CONCLUSAO' => 31.00, 'TECNICO' => 'Carlos Silva'],
        ],
        'media_atendimento'  => 2.33,
        'media_conclusao'    => 13.25,
        'dados_tecnico_list' => [
            ['TECNICO' => 'Carlos Silva', 'MEDIA_ATENDIMENTO' => 2.33, 'MEDIA_CONCLUSAO' => 13.25, 'TEMPO_ATENDIMENTO' => 7.00, 'QTD_ATENDIMENTO' => 3, 'TEMPO_CONCLUSAO' => 39.75],
        ],
    ],
    [
        'name'  => 'Marcos Oliveira',
        'title' => 'CONTROLE DE TEMPO DE CHAMADO X ATENDIMENTO — MARCOS OLIVEIRA',
        'dados_list' => [
            ['DATA_CHAMADO' => '2024-03-02 10:00', 'FLAG_MAQUINA_PARADA' => 'NÃO', 'NUMERO_OS' => 'OS-1002', 'TIPO_OS' => 'Preventiva', 'STATUS_OS' => 'Concluída', 'CLIENTE' => 'Fazenda Boa Vista',   'DATA_ATENDIMENTO' => '2024-03-02 12:30', 'TEMPO_ATENDIMENTO' => 2.50, 'DATA_CONCLUSAO' => '2024-03-02 18:00', 'TEMPO_CONCLUSAO' => 5.50,  'TECNICO' => 'Marcos Oliveira'],
            ['DATA_CHAMADO' => '2024-03-08 07:00', 'FLAG_MAQUINA_PARADA' => 'SIM',  'NUMERO_OS' => 'OS-1008', 'TIPO_OS' => 'Corretiva',  'STATUS_OS' => 'Concluída', 'CLIENTE' => 'Distribuidora Leste', 'DATA_ATENDIMENTO' => '2024-03-08 10:00', 'TEMPO_ATENDIMENTO' => 3.00, 'DATA_CONCLUSAO' => '2024-03-08 16:30', 'TEMPO_CONCLUSAO' => 33.00, 'TECNICO' => 'Marcos Oliveira'],
            ['DATA_CHAMADO' => '2024-03-09 09:30', 'FLAG_MAQUINA_PARADA' => 'NÃO', 'NUMERO_OS' => 'OS-1009', 'TIPO_OS' => 'Revisão',    'STATUS_OS' => 'Concluída', 'CLIENTE' => 'Porto Seco Sul',      'DATA_ATENDIMENTO' => '2024-03-09 11:00', 'TEMPO_ATENDIMENTO' => 1.50, 'DATA_CONCLUSAO' => '2024-03-09 14:00', 'TEMPO_CONCLUSAO' => 3.00,  'TECNICO' => 'Marcos Oliveira'],
        ],
        'media_atendimento'  => 2.33,
        'media_conclusao'    => 13.83,
        'dados_tecnico_list' => [
            ['TECNICO' => 'Marcos Oliveira', 'MEDIA_ATENDIMENTO' => 2.33, 'MEDIA_CONCLUSAO' => 13.83, 'TEMPO_ATENDIMENTO' => 7.00, 'QTD_ATENDIMENTO' => 3, 'TEMPO_CONCLUSAO' => 41.50],
        ],
    ],
    [
        'name'  => 'Ana Ferreira',
        'title' => 'CONTROLE DE TEMPO DE CHAMADO X ATENDIMENTO — ANA FERREIRA',
        'dados_list' => [
            ['DATA_CHAMADO' => '2024-03-03 07:30', 'FLAG_MAQUINA_PARADA' => 'SIM',  'NUMERO_OS' => 'OS-1003', 'TIPO_OS' => 'Corretiva',  'STATUS_OS' => 'Em andamento', 'CLIENTE' => 'Transportes Rápido', 'DATA_ATENDIMENTO' => '2024-03-03 11:00', 'TEMPO_ATENDIMENTO' => 3.50, 'DATA_CONCLUSAO' => '2024-03-03 17:30', 'TEMPO_CONCLUSAO' => 32.00, 'TECNICO' => 'Ana Ferreira'],
            ['DATA_CHAMADO' => '2024-03-10 08:00', 'FLAG_MAQUINA_PARADA' => 'NÃO', 'NUMERO_OS' => 'OS-1010', 'TIPO_OS' => 'Preventiva', 'STATUS_OS' => 'Concluída',    'CLIENTE' => 'Agropecuária Verde', 'DATA_ATENDIMENTO' => '2024-03-10 09:30', 'TEMPO_ATENDIMENTO' => 1.50, 'DATA_CONCLUSAO' => '2024-03-10 12:00', 'TEMPO_CONCLUSAO' => 2.50,  'TECNICO' => 'Ana Ferreira'],
            ['DATA_CHAMADO' => '2024-03-11 13:00', 'FLAG_MAQUINA_PARADA' => 'SIM',  'NUMERO_OS' => 'OS-1011', 'TIPO_OS' => 'Corretiva',  'STATUS_OS' => 'Concluída',    'CLIENTE' => 'Usina Esperança',    'DATA_ATENDIMENTO' => '2024-03-11 16:00', 'TEMPO_ATENDIMENTO' => 3.00, 'DATA_CONCLUSAO' => '2024-03-11 20:30', 'TEMPO_CONCLUSAO' => 34.00, 'TECNICO' => 'Ana Ferreira'],
        ],
        'media_atendimento'  => 2.67,
        'media_conclusao'    => 22.83,
        'dados_tecnico_list' => [
            ['TECNICO' => 'Ana Ferreira', 'MEDIA_ATENDIMENTO' => 2.67, 'MEDIA_CONCLUSAO' => 22.83, 'TEMPO_ATENDIMENTO' => 8.00, 'QTD_ATENDIMENTO' => 3, 'TEMPO_CONCLUSAO' => 68.50],
        ],
    ],
    [
        'name'  => 'João e Paula',
        'title' => 'CONTROLE DE TEMPO DE CHAMADO X ATENDIMENTO — JOÃO E PAULA',
        'dados_list' => [
            ['DATA_CHAMADO' => '2024-03-04 09:00', 'FLAG_MAQUINA_PARADA' => 'NÃO', 'NUMERO_OS' => 'OS-1004', 'TIPO_OS' => 'Revisão',   'STATUS_OS' => 'Concluída', 'CLIENTE' => 'Mineradora Delta', 'DATA_ATENDIMENTO' => '2024-03-04 10:00', 'TEMPO_ATENDIMENTO' => 1.00, 'DATA_CONCLUSAO' => '2024-03-04 13:00', 'TEMPO_CONCLUSAO' => 3.00,  'TECNICO' => 'João Pereira'],
            ['DATA_CHAMADO' => '2024-03-05 13:00', 'FLAG_MAQUINA_PARADA' => 'SIM',  'NUMERO_OS' => 'OS-1005', 'TIPO_OS' => 'Corretiva', 'STATUS_OS' => 'Concluída', 'CLIENTE' => 'Logística Sul',    'DATA_ATENDIMENTO' => '2024-03-05 16:30', 'TEMPO_ATENDIMENTO' => 3.50, 'DATA_CONCLUSAO' => '2024-03-05 20:00', 'TEMPO_CONCLUSAO' => 35.00, 'TECNICO' => 'Paula Ramos'],
            ['DATA_CHAMADO' => '2024-03-12 08:30', 'FLAG_MAQUINA_PARADA' => 'NÃO', 'NUMERO_OS' => 'OS-1012', 'TIPO_OS' => 'Revisão',   'STATUS_OS' => 'Concluída', 'CLIENTE' => 'Cerealista Norte',  'DATA_ATENDIMENTO' => '2024-03-12 10:00', 'TEMPO_ATENDIMENTO' => 1.50, 'DATA_CONCLUSAO' => '2024-03-12 13:30', 'TEMPO_CONCLUSAO' => 3.50,  'TECNICO' => 'João Pereira'],
            ['DATA_CHAMADO' => '2024-03-13 11:00', 'FLAG_MAQUINA_PARADA' => 'SIM',  'NUMERO_OS' => 'OS-1013', 'TIPO_OS' => 'Corretiva', 'STATUS_OS' => 'Concluída', 'CLIENTE' => 'Armazém Central',  'DATA_ATENDIMENTO' => '2024-03-13 14:30', 'TEMPO_ATENDIMENTO' => 3.50, 'DATA_CONCLUSAO' => '2024-03-13 19:00', 'TEMPO_CONCLUSAO' => 32.00, 'TECNICO' => 'Paula Ramos'],
        ],
        'media_atendimento'  => 2.38,
        'media_conclusao'    => 18.38,
        'dados_tecnico_list' => [
            ['TECNICO' => 'João Pereira', 'MEDIA_ATENDIMENTO' => 1.25, 'MEDIA_CONCLUSAO' => 3.25,  'TEMPO_ATENDIMENTO' => 2.50, 'QTD_ATENDIMENTO' => 2, 'TEMPO_CONCLUSAO' => 6.50],
            ['TECNICO' => 'Paula Ramos',  'MEDIA_ATENDIMENTO' => 3.50, 'MEDIA_CONCLUSAO' => 33.50, 'TEMPO_ATENDIMENTO' => 7.00, 'QTD_ATENDIMENTO' => 2, 'TEMPO_CONCLUSAO' => 67.00],
        ],
    ],
];

// ── Build all sheets ──────────────────────────────────────────────────────────

foreach ($sheets as $index => $sheetData) {
    $sheet->createSheet($sheetData['name'], $index);
    $row = 1;

    // Column widths
    $sheet->autoSize();
    $sheet->setColWidth('A', 100);

    // Title
    $sheet->mergeCells("A{$row}:K{$row}", $sheetData['title']);
    $sheet->getStyle("A{$row}:K{$row}")->applyFromArray($styleTitle);
    $sheet->setRowHeight($row, 40);
    $row++;

    // Header
    $sheet->getStyle("A{$row}:K{$row}")->applyFromArray($styleTitleCenter);
    $sheet->setCellValue("A{$row}", 'DATA/HORA DO CHAMADO');
    $sheet->setCellValue("B{$row}", 'MAQUINA PARADA?');
    $sheet->setCellValue("C{$row}", 'No O.S.');
    $sheet->setCellValue("D{$row}", 'TIPO O.S.');
    $sheet->setCellValue("E{$row}", 'STATUS O.S.');
    $sheet->setCellValue("F{$row}", 'CLIENTE');
    $sheet->setCellValue("G{$row}", 'DATA/HORA ATENDIMENTO');
    $sheet->setCellValue("H{$row}", 'TEMPO DE ATENDIMENTO');
    $sheet->setCellValue("I{$row}", 'DATA/HORA CONCLUSÃO DO SERVIÇO');
    $sheet->setCellValue("J{$row}", 'TEMPO DE CONCLUSÃO DO SERVIÇO');
    $sheet->setCellValue("K{$row}", 'PRODUTIVO RESPONSÁVEL');
    $row++;

    // Data rows
    foreach ($sheetData['dados_list'] as $r) {
        $styleTempoAtendimento = $r['TEMPO_ATENDIMENTO'] >= 2  ? $styleCenterRed : $styleCenterWhite;
        $styleTempoConclusao   = $r['TEMPO_CONCLUSAO']   >= 30 ? $styleCenterRed : $styleCenterWhite;

        $sheet->getStyle("A{$row}:K{$row}")->applyFromArray($styleCenterWhite);
        $sheet->setCellValue("A{$row}", $r['DATA_CHAMADO']);
        $sheet->setCellValue("B{$row}", $r['FLAG_MAQUINA_PARADA']);
        $sheet->setCellValue("C{$row}", $r['NUMERO_OS']);
        $sheet->setCellValue("D{$row}", $r['TIPO_OS']);
        $sheet->setCellValue("E{$row}", $r['STATUS_OS']);
        $sheet->setCellValue("F{$row}", $r['CLIENTE']);
        $sheet->setCellValue("G{$row}", $r['DATA_ATENDIMENTO']);
        $sheet->getStyle("H{$row}")->applyFromArray($styleTempoAtendimento);
        $sheet->setCellValue("H{$row}", $r['TEMPO_ATENDIMENTO']);
        $sheet->setNumberFormat("H{$row}", '0.00');
        $sheet->setCellValue("I{$row}", $r['DATA_CONCLUSAO']);
        $sheet->getStyle("J{$row}")->applyFromArray($styleTempoConclusao);
        $sheet->setCellValue("J{$row}", $r['TEMPO_CONCLUSAO']);
        $sheet->setNumberFormat("J{$row}", '0.00');
        $sheet->setCellValue("K{$row}", $r['TECNICO']);
        $row++;
    }

    // General average
    $sheet->getStyle("A{$row}:K{$row}")->applyFromArray($styleTitleCenter);
    $sheet->mergeCells("A{$row}:E{$row}");
    $sheet->mergeCells("F{$row}:G{$row}", 'MEDIA GERAL');
    $sheet->getStyle("H{$row}")->applyFromArray($styleCenterYellow);
    $sheet->setCellValue("H{$row}", $sheetData['media_atendimento']);
    $sheet->setNumberFormat("H{$row}", '0.00');
    $sheet->setCellValue("I{$row}", '');
    $sheet->getStyle("J{$row}")->applyFromArray($styleCenterYellow);
    $sheet->setCellValue("J{$row}", $sheetData['media_conclusao']);
    $sheet->setNumberFormat("J{$row}", '0.00');
    $sheet->setCellValue("K{$row}", '');
    $row++;

    // Separator
    $sheet->mergeCells("A{$row}:K{$row}");
    $row++;

    // Per-technician section header
    $sheet->mergeCells("A{$row}:E{$row}");
    $sheet->getStyle("F{$row}:H{$row}")->applyFromArray($styleTitleCenter);
    $sheet->mergeCells("F{$row}:H{$row}", 'CONTROLE DE MEDIA POR TECNICO');
    $sheet->getStyle("I{$row}:K{$row}")->applyFromArray($styleTitleCenterGray);
    $sheet->mergeCells("I{$row}:K{$row}", 'DADOS');
    $row++;

    $sheet->mergeCells("A{$row}:E{$row}");
    $sheet->getStyle("F{$row}:H{$row}")->applyFromArray($styleTitleCenter);
    $sheet->setCellValue("F{$row}", 'NOME');
    $sheet->setCellValue("G{$row}", 'ATENDIMENTO');
    $sheet->setCellValue("H{$row}", 'CONCLUSÃO REPARO');
    $sheet->getStyle("I{$row}:K{$row}")->applyFromArray($styleTitleCenterGray);
    $sheet->setCellValue("I{$row}", 'TOTAL DE TEMPO DE ATENDIMENTO');
    $sheet->setCellValue("J{$row}", 'TOTAL DE ATENDIMENTOS');
    $sheet->setCellValue("K{$row}", 'TOTAL DE TEMPO DE REPARO');
    $row++;

    // Per-technician data rows
    foreach ($sheetData['dados_tecnico_list'] as $f) {
        $sheet->mergeCells("A{$row}:E{$row}");
        $sheet->getStyle("F{$row}:H{$row}")->applyFromArray($styleCenterWhite);
        $sheet->setCellValue("F{$row}", $f['TECNICO']);
        $sheet->setCellValue("G{$row}", $f['MEDIA_ATENDIMENTO']);
        $sheet->setNumberFormat("G{$row}", '0.00');
        $sheet->setCellValue("H{$row}", $f['MEDIA_CONCLUSAO']);
        $sheet->setNumberFormat("H{$row}", '0.00');
        $sheet->getStyle("I{$row}:K{$row}")->applyFromArray($styleCenterGray);
        $sheet->setCellValue("I{$row}", $f['TEMPO_ATENDIMENTO']);
        $sheet->setNumberFormat("I{$row}", '0.00');
        $sheet->setCellValue("J{$row}", $f['QTD_ATENDIMENTO']);
        $sheet->setCellValue("K{$row}", $f['TEMPO_CONCLUSAO']);
        $sheet->setNumberFormat("K{$row}", '0.00');
        $row++;
    }
}

// Back to first sheet before saving
$sheet->setActiveSheetByIndex(0);