Skip to content

Commit

Permalink
feat: add project
Browse files Browse the repository at this point in the history
  • Loading branch information
bernardofernandezz committed Nov 14, 2024
1 parent 668d29b commit 54be72f
Show file tree
Hide file tree
Showing 11 changed files with 438 additions and 0 deletions.
Binary file added __pycache__/main.cpython-312.pyc
Binary file not shown.
191 changes: 191 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
from fastapi import FastAPI, UploadFile, File, HTTPException, Request, Form
from fastapi.responses import FileResponse, HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
import pandas as pd
import json
import os
from datetime import datetime
from typing import Dict, Any
from openpyxl import load_workbook
from openpyxl.styles import PatternFill, Font, Alignment, Border, Side
from openpyxl.worksheet.table import Table, TableStyleInfo

app = FastAPI(
title="API Conversor JSON para Excel",
description="API para converter arquivos JSON em Excel",
version="1.0.0"
)

# Configurar arquivos estáticos e templates
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")

# Rota principal - renderiza o template HTML
@app.get("/", response_class=HTMLResponse)
async def home(request: Request):
return templates.TemplateResponse("index.html", {"request": request})

@app.post("/convert/file/")
async def convert_file(json_file: UploadFile = File(...)):
"""
Converte um arquivo JSON enviado para Excel
"""
if not json_file.filename.endswith(".json"):
raise HTTPException(status_code=400, detail="Por favor, envie um arquivo JSON.")

try:
content = await json_file.read()
json_data = json.loads(content.decode('utf-8'))
return await convert_json_to_excel(json_data, json_file.filename)

except json.JSONDecodeError:
raise HTTPException(status_code=400, detail="Arquivo JSON inválido")
except Exception as e:
raise HTTPException(status_code=500, detail=f"Erro ao processar arquivo: {str(e)}")

@app.post("/convert/json/")
async def convert_json(json_text: str = Form(...)):
"""
Converte JSON enviado diretamente no body para Excel
"""
try:
# Tenta fazer o parse do JSON
json_data = json.loads(json_text)

# Verifica se o JSON é um dicionário ou lista
if not isinstance(json_data, (dict, list)):
raise HTTPException(status_code=400, detail="O JSON deve ser um objeto ou array")

# Se for uma lista de objetos, converte diretamente
if isinstance(json_data, list):
df = pd.DataFrame(json_data)
# Se for um objeto único, converte para lista com um item
else:
df = pd.DataFrame([json_data])

# Cria nome único para o arquivo Excel
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
excel_filename = f"uploads/json_data_{timestamp}.xlsx"

# Cria diretório se não existir
os.makedirs("uploads", exist_ok=True)

# Salva como Excel
df.to_excel(excel_filename, index=False)

# Retorna o arquivo Excel
return FileResponse(
path=excel_filename,
filename=os.path.basename(excel_filename),
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)

except json.JSONDecodeError:
raise HTTPException(status_code=400, detail="JSON inválido")
except Exception as e:
raise HTTPException(status_code=500, detail=f"Erro ao processar JSON: {str(e)}")

async def convert_json_to_excel(json_data: dict, original_filename: str):
"""
Função auxiliar para converter JSON em Excel com formatação adequada
"""
try:
# Converte JSON para DataFrame
if isinstance(json_data, list):
df = pd.DataFrame(json_data)
else:
df = pd.DataFrame([json_data])

# Cria nome único para o arquivo Excel
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
excel_filename = f"uploads/{os.path.splitext(original_filename)[0]}_{timestamp}.xlsx"

# Cria diretório se não existir
os.makedirs("uploads", exist_ok=True)

# Salva primeiro sem índice
df.to_excel(excel_filename, index=False)

# Carrega o workbook para formatação
wb = load_workbook(excel_filename)
ws = wb.active

# Define estilos
header_fill = PatternFill(start_color="1F4E78", end_color="1F4E78", fill_type="solid")
header_font = Font(name='Arial', size=11, color="FFFFFF", bold=True)
cell_font = Font(name='Arial', size=10)

# Aplica estilos ao cabeçalho
for cell in ws[1]:
cell.fill = header_fill
cell.font = header_font
cell.alignment = Alignment(horizontal='center', vertical='center', wrap_text=True)

# Aplica estilos às células de dados
for row in ws.iter_rows(min_row=2):
for cell in row:
cell.font = cell_font
cell.alignment = Alignment(horizontal='left', vertical='center', wrap_text=True)
cell.border = Border(
left=Side(style='thin', color='D4D4D4'),
right=Side(style='thin', color='D4D4D4'),
top=Side(style='thin', color='D4D4D4'),
bottom=Side(style='thin', color='D4D4D4')
)

# Ajusta largura das colunas
for column in ws.columns:
max_length = 0
column_letter = column[0].column_letter

for cell in column:
try:
if len(str(cell.value)) > max_length:
max_length = len(str(cell.value))
except:
pass

adjusted_width = min(max_length + 2, 50) # Limita a largura máxima
ws.column_dimensions[column_letter].width = adjusted_width

# Remove tabela existente se houver
if len(ws.tables) > 0:
del ws.tables[ws.tables.keys()[0]]

# Cria uma nova tabela
table_name = f"Table_{timestamp}" # Use uma variável timestamp para nome único
tab = Table(displayName=table_name, ref=ws.dimensions)

# Aplica estilo à tabela
style = TableStyleInfo(
name="TableStyleMedium2",
showFirstColumn=False,
showLastColumn=False,
showRowStripes=True,
showColumnStripes=False
)
tab.tableStyleInfo = style

# Adiciona a tabela
ws.add_table(tab)

# Congela o painel
ws.freeze_panes = 'A2'

# Salva as alterações
wb.save(excel_filename)

# Retorna o arquivo Excel
return FileResponse(
path=excel_filename,
filename=os.path.basename(excel_filename),
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)

except Exception as e:
raise HTTPException(status_code=500, detail=f"Erro ao formatar Excel: {str(e)}")

if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
125 changes: 125 additions & 0 deletions static/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

body {
font-family: Arial, sans-serif;
background-color: #f5f5f5;
color: #333;
line-height: 1.6;
}

.container {
max-width: 800px;
margin: 2rem auto;
padding: 0 1rem;
}

h1 {
text-align: center;
color: #2c3e50;
margin-bottom: 2rem;
}

.converter-box {
background: white;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
padding: 2rem;
}

.tab-container {
display: flex;
margin-bottom: 1rem;
border-bottom: 2px solid #eee;
}

.tab-button {
padding: 0.5rem 1rem;
border: none;
background: none;
cursor: pointer;
font-size: 1rem;
color: #666;
transition: all 0.3s ease;
}

.tab-button.active {
color: #2980b9;
border-bottom: 2px solid #2980b9;
margin-bottom: -2px;
}

.tab-content {
display: none;
}

.tab-content.active {
display: block;
}

.upload-form {
display: flex;
flex-direction: column;
gap: 1rem;
}

.drop-zone {
border: 2px dashed #ccc;
border-radius: 5px;
padding: 2rem;
text-align: center;
cursor: pointer;
transition: border 0.3s ease;
}

.drop-zone:hover {
border-color: #2980b9;
}

.drop-zone-input {
display: none;
}

.json-input {
width: 100%;
height: 200px;
padding: 1rem;
border: 1px solid #ccc;
border-radius: 5px;
resize: vertical;
font-family: monospace;
}

.convert-button {
background-color: #2980b9;
color: white;
border: none;
padding: 1rem;
border-radius: 5px;
cursor: pointer;
font-size: 1rem;
transition: background-color 0.3s ease;
}

.convert-button:hover {
background-color: #2471a3;
}

.drop-zone-hover {
border-color: #2980b9;
background-color: #2980b91a;
}

.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
}
Loading

0 comments on commit 54be72f

Please sign in to comment.