From 0c4b690099ef5026e0bec9b13d51b79ada65ca62 Mon Sep 17 00:00:00 2001 From: Suzane Duarte Date: Sun, 25 Jun 2023 21:25:12 -0300 Subject: [PATCH 1/2] =?UTF-8?q?#135=20feat:=20adicionando=20termo=20de=20o?= =?UTF-8?q?rdem=20de=20servi=C3=A7o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/order-service-pdf/index.tsx | 393 ++++++++++++++++++ .../order-service-term-modal/index.tsx | 77 ++++ .../order-service/OrderServiceControl.tsx | 55 ++- 3 files changed, 519 insertions(+), 6 deletions(-) create mode 100644 src/components/order-service-pdf/index.tsx create mode 100644 src/components/order-service-term-modal/index.tsx diff --git a/src/components/order-service-pdf/index.tsx b/src/components/order-service-pdf/index.tsx new file mode 100644 index 00000000..c26040d8 --- /dev/null +++ b/src/components/order-service-pdf/index.tsx @@ -0,0 +1,393 @@ +import { + Document, + Page, + Text, + View, + StyleSheet, + Image, + Font, +} from '@react-pdf/renderer'; +import { OrderServiceData } from '@/pages/order-service/OrderServiceControl'; +import { OSStatusMap } from '@/constants/orderservice'; +import { useAuth } from '@/contexts/AuthContext'; + +interface OrderServicePdfProps { + orderService: OrderServiceData; +} + +Font.register({ + family: 'Arial', + fonts: [ + { + src: 'https://fonts.cdnfonts.com/s/29107/ARIALMTMEDIUM.woff', + fontStyle: 'normal', + fontWeight: 400, + }, + { + src: 'https://fonts.cdnfonts.com/s/29107/ARIALBOLDMT.woff', + fontStyle: 'bold', + fontWeight: 700, + }, + ], +}); + +export function OrderServicePdf({ orderService }: OrderServicePdfProps) { + const { user } = useAuth(); + + const styles = StyleSheet.create({ + page: { + flexDirection: 'column', + padding: 20, + fontFamily: 'Arial', + }, + header: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + }, + caption: { + fontSize: 8, + }, + overtitle: { + paddingTop: 24, + fontSize: 16, + fontWeight: 700, + }, + title: { + paddingTop: 8, + fontSize: 20, + fontWeight: 700, + }, + subtitle: { + fontSize: 12, + paddingBottom: 12, + }, + locale: { + paddingBottom: 16, + alignSelf: 'flex-end', + textAlign: 'right', + fontSize: 12, + fontWeight: 700, + }, + logo: { + width: 50, + height: 66, + marginBottom: 4, + }, + tableHeader: { + backgroundColor: '#D8D8D8', + flexDirection: 'row', + borderBottomWidth: 1, + borderTopWidth: 1, + borderBottomColor: '#000', + minHeight: 24, + alignItems: 'center', + borderLeftWidth: 1, + borderLeftColor: '#e2e2e2', + }, + columnHeader: { + color: '#000', + flex: 1, + textAlign: 'center', + fontSize: 8, + fontWeight: 'bold', + height: '100%', + paddingTop: 8, + borderRightWidth: 1, + borderRightColor: '#e2e2e2', + }, + tableRow: { + flexDirection: 'row', + borderBottomWidth: 1, + borderBottomColor: '#000', + minHeight: 24, + alignItems: 'center', + borderLeftWidth: 1, + borderLeftColor: '#e2e2e2', + }, + rowData: { + fontSize: 8, + flex: 1, + textAlign: 'center', + borderRightWidth: 1, + borderRightColor: '#e2e2e2', + borderLeftColor: '#e2e2e2', + paddingTop: 16, + minHeight: 44, + }, + signature: { + marginTop: 200, + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + }, + }); + + const currentEmissionDate = new Date(); + const emissionDay = currentEmissionDate.getDate().toString().padStart(2, '0'); + const emissionMonth = currentEmissionDate + .getMonth() + .toString() + .padStart(2, '0'); + const emissionYear = currentEmissionDate + .getFullYear() + .toString() + .padStart(4, '0'); + + const emissionHours = currentEmissionDate + .getHours() + .toString() + .padStart(2, '0'); + const emissionMinutes = currentEmissionDate + .getMinutes() + .toString() + .padStart(2, '0'); + const emissionSeconds = currentEmissionDate + .getSeconds() + .toString() + .padStart(2, '0'); + + const formattedEmissionDate = `${emissionDay}/${emissionMonth}/${emissionYear} - ${emissionHours}:${emissionMinutes}:${emissionSeconds}`; + + const receiverDateYear = new Date(orderService.finishDate) + ?.getFullYear() + .toString() + .padStart(4, '0'); + const receiverDateMonth = new Date(orderService.finishDate) + ?.getMonth() + .toString() + .padStart(2, '0'); + const receiverDateDay = new Date(orderService.finishDate) + ?.getDay() + .toString() + .padStart(2, '0'); + + const formattedReceiverDate = `${receiverDateDay}/${receiverDateMonth}/${receiverDateYear}`; + + return ( + + + + + ESTADO DE GOIÁS + DIRETORIA GERAL DA POLÍCIA CIVIL + + + SUPERINTENDÊNCIA DE GESTÃO INTEGRADA + + + + DIVISÃO DE SUPORTE TÉCNICO EM INFORMÁTICA + + O.S. nº {orderService.id} + + {orderService.equipment.unit.localization} + + + Sistema de Controle Interno da DSTI + + + + Status da O.S.: {OSStatusMap.get(orderService.status)} + + + + Data da emissão: {formattedEmissionDate} + + Recebido por: {user?.name} + + + Entrada do Equipamento + + + + + Tombamento + + + Tipo + + Marca + Descrição + + Processo SEI + + + Servidor + + + Funcional ou CPF + + + + + {orderService.equipment?.tippingNumber} + + + {orderService.equipment?.type} + + + {orderService.equipment?.brand?.name} + + + {orderService.equipment.description} + + + {orderService.seiProcess} + + + <> + {'_'.repeat(20)} + {'\n'} + {orderService.senderName} + + + + {orderService.senderDocument} + + + + + Relato do defeito + + + + + {orderService.description} + + + + {OSStatusMap.get(orderService.status) === 'Concluído' && ( + <> + + + Retirada do equipamento + + + + + Data da retirada + + + Servidor responsável pela retirada + + + Funcional / CPF + + + Técnico responsável pela manutenção + + + + + {formattedReceiverDate} + + + <> + {'_'.repeat(20)} + {'\n'} + {orderService.withdrawalName} + + + + {orderService.withdrawalDocument} + + + {orderService.technicianName} + + + + )} + + + Endereço Av. Anhanguera, 7364 - Aeroviario, Goiânia - GO, 74435-300. + {'\n'} + Fone: 3201-6184 + + + + ); +} diff --git a/src/components/order-service-term-modal/index.tsx b/src/components/order-service-term-modal/index.tsx new file mode 100644 index 00000000..2a8020a2 --- /dev/null +++ b/src/components/order-service-term-modal/index.tsx @@ -0,0 +1,77 @@ +import { Flex, Text, Button } from '@chakra-ui/react'; +import { PDFDownloadLink } from '@react-pdf/renderer'; +import { MdDescription } from 'react-icons/md'; +import { Modal } from '../modal'; +import { OrderServiceData } from '@/pages/order-service/OrderServiceControl'; +import { OSStatusMap } from '@/constants/orderservice'; +import { OrderServicePdf } from '../order-service-pdf'; + +type OrderServiceTermModalProps = { + isOpen: boolean; + onClose(): void; + selectedOrderService: OrderServiceData; + refreshRequest: boolean; + setRefreshRequest: React.Dispatch>; +}; + +export function OrderServiceTermModal({ + isOpen, + onClose, + selectedOrderService, + refreshRequest, + setRefreshRequest, +}: OrderServiceTermModalProps) { + const onCloseCallback = () => { + onClose(); + }; + + if (!selectedOrderService) { + return null; + } + + return ( + + + + termo_de_{OSStatusMap.get(selectedOrderService.status)}.pdf + + + + + } + fileName={`termo_de_${OSStatusMap.get(selectedOrderService.status)}`} + > + {({ loading }) => ( + + )} + + + + ); +} diff --git a/src/pages/order-service/OrderServiceControl.tsx b/src/pages/order-service/OrderServiceControl.tsx index 93d37112..adc2541a 100644 --- a/src/pages/order-service/OrderServiceControl.tsx +++ b/src/pages/order-service/OrderServiceControl.tsx @@ -19,7 +19,7 @@ import { useDisclosure, } from '@chakra-ui/react'; import { AxiosResponse } from 'axios'; -import { FaTools } from 'react-icons/fa'; +import { FaFileAlt, FaTools } from 'react-icons/fa'; import { toast } from '@/utils/toast'; import { SideBar } from '@/components/side-bar'; import { api, apiSchedula } from '../../config/lib/axios'; @@ -35,6 +35,7 @@ import { OSStatusMap, OSStatusStyleMap } from '@/constants/orderservice'; import { NewControlledSelect } from '@/components/form-fields/new-controlled-select'; import { OrderServiceEditModal } from '@/components/order-service-edit-modal'; import { OrderServiceRegisterModal } from '@/components/order-service-register-modal'; +import { OrderServiceTermModal } from '@/components/order-service-term-modal'; interface ISelectOption { label: string; @@ -42,6 +43,7 @@ interface ISelectOption { } export interface Equipment { + description: string; tippingNumber: string; serialNumber: string; brand: { @@ -61,16 +63,20 @@ export interface OrderServiceData { date: string; description?: string; authorId: string; - receiverName: string; + withdrawalName: string; sender?: string; equipmentSnapshot: any; senderFunctionalNumber: string; createdAt: string; updatedAt: string; equipment: Equipment; + seiProcess: string; + senderName: string; + senderDocument: string; history: History; - receiverFunctionalNumber: string; - technicians?: string[]; + withdrawalDocument: string; + technicianId: string; + technicianName: string; status: string; unit: { name: string; @@ -79,7 +85,7 @@ export interface OrderServiceData { brand: { name: string; }; - receiverDate?: Date; + finishDate: string; } type FilterValues = { @@ -117,6 +123,8 @@ function OrderServiceTable() { const [selectedOrderServiceToEdit, setSelectedOrderServiceToEdit] = useState(); + const [selectedOrderServiceToPrint, setSelectedOrderServiceToPrint] = + useState(); const { isOpen, onClose, onOpen } = useDisclosure(); const { @@ -124,6 +132,11 @@ function OrderServiceTable() { onClose: onCloseEditOrderService, onOpen: onOpenEditOrderService, } = useDisclosure(); + const { + isOpen: isOpenPrintOrderService, + onClose: onClosePrintOrderService, + onOpen: onOpenPrintOrderService, + } = useDisclosure(); const { control, @@ -136,10 +149,21 @@ function OrderServiceTable() { const watchFilter = watch(); const handleEdit = (orderService: OrderServiceData) => { - if (orderService) setSelectedOrderServiceToEdit(orderService); + if (orderService) { + setSelectedOrderServiceToEdit(orderService); + } + onOpenEditOrderService(); }; + const handlePrint = (orderService: OrderServiceData) => { + if (orderService) { + setSelectedOrderServiceToPrint(orderService); + } + + onOpenPrintOrderService(); + }; + const handleFilterChange = () => { const { type, dateOS, status, unit } = watchFilter; @@ -381,6 +405,7 @@ function OrderServiceTable() { Status da OS Data da OS + @@ -424,6 +449,17 @@ function OrderServiceTable() { /> + + } + onClick={(event) => { + event.stopPropagation(); + handlePrint(orderService); + }} + /> + ))} @@ -479,6 +515,13 @@ function OrderServiceTable() { refreshRequest={refreshRequest} setRefreshRequest={setRefreshRequest} /> + ); From de82721d30f966cc8a03ac08bc11fc0faa215aa9 Mon Sep 17 00:00:00 2001 From: Suzane Duarte Date: Mon, 26 Jun 2023 11:52:13 -0300 Subject: [PATCH 2/2] =?UTF-8?q?#135=20feat:=20adicionando=20testes=20para?= =?UTF-8?q?=20gera=C3=A7=C3=A3o=20de=20ordem=20de=20servi=C3=A7o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../order-service-register-form/index.tsx | 3 + .../order-service-register-modal/index.tsx | 3 + .../order-service/OrderServiceControl.tsx | 1 + src/pages/order-service/orderservice.spec.tsx | 118 ++++++++++++++++++ 4 files changed, 125 insertions(+) create mode 100644 src/pages/order-service/orderservice.spec.tsx diff --git a/src/components/order-service-register-form/index.tsx b/src/components/order-service-register-form/index.tsx index b22b5be4..1bed2c8b 100644 --- a/src/components/order-service-register-form/index.tsx +++ b/src/components/order-service-register-form/index.tsx @@ -38,12 +38,14 @@ interface OrderServiceFormProps { onClose: () => void; refreshRequest: boolean; setRefreshRequest: React.Dispatch>; + onOpenTerm(): void; } export function OrderServiceRegisterForm({ onClose, refreshRequest, setRefreshRequest, + onOpenTerm, }: OrderServiceFormProps) { const [selectedEquipment, setSelectedEquipment] = useState(); const [equipments, setEquipments] = useState([]); @@ -147,6 +149,7 @@ export function OrderServiceRegisterForm({ toast.success('Ordem de serviço cadastrada com sucesso', 'Sucesso'); setRefreshRequest(!refreshRequest); onClose(); + onOpenTerm(); } catch (error: any) { console.error(error); const message = error.response.data.error diff --git a/src/components/order-service-register-modal/index.tsx b/src/components/order-service-register-modal/index.tsx index 81ef5450..ee74ef9b 100644 --- a/src/components/order-service-register-modal/index.tsx +++ b/src/components/order-service-register-modal/index.tsx @@ -7,6 +7,7 @@ type OrderServiceRegisterModalProps = { onClose(): void; refreshRequest: boolean; setRefreshRequest: React.Dispatch>; + onOpenTerm(): void; }; export function OrderServiceRegisterModal({ @@ -14,6 +15,7 @@ export function OrderServiceRegisterModal({ onClose, refreshRequest, setRefreshRequest, + onOpenTerm, }: OrderServiceRegisterModalProps) { return ( diff --git a/src/pages/order-service/OrderServiceControl.tsx b/src/pages/order-service/OrderServiceControl.tsx index adc2541a..eb59b5d3 100644 --- a/src/pages/order-service/OrderServiceControl.tsx +++ b/src/pages/order-service/OrderServiceControl.tsx @@ -514,6 +514,7 @@ function OrderServiceTable() { isOpen={isOpen} refreshRequest={refreshRequest} setRefreshRequest={setRefreshRequest} + onOpenTerm={onOpenPrintOrderService} /> ({ PDFDownloadLink: vi.fn() })); + +const renderComponent = () => + render( + + + + } /> + + + + ); + +describe('Order Service', () => { + it('should display a list', async () => { + const { findByRole } = renderComponent(); + + const list = await findByRole('table'); + expect(list).toBeInTheDocument(); + }); + + it('should display a search bar', async () => { + const { findByPlaceholderText } = renderComponent(); + + const bar = await findByPlaceholderText('Pesquisa'); + expect(bar).toBeInTheDocument(); + }); + + it('should select service order and open modal with correct data', async () => { + const mockedGet = vi.spyOn(api, 'get'); + const mockedPdfDownloadLink = vi.spyOn( + ReactPdf, + 'PDFDownloadLink' + ) as SpyInstance; + mockedPdfDownloadLink.mockReturnValue(null); + mockedGet.mockReturnValue(Promise.resolve(ORDERSERVICE_RESPONSE_MOCK)); + const { findByLabelText, findByText } = renderComponent(); + + await act(async () => { + await new Promise((r) => { + setTimeout(r, 1000); + }); + }); + + const orderServiceDetailButton = await findByLabelText( + 'Gerar termo de ordem de serviço' + ); + + await fireEvent.click(orderServiceDetailButton); + const orderServicePdfText = await findByText('termo_de_Em manutenção.pdf'); + + expect(orderServicePdfText).toBeInTheDocument(); + }); +});