From 052c02ed1adf923c857f2717c572e021f9ebb568 Mon Sep 17 00:00:00 2001
From: Rodribm10 <160369324+Rodribm10@users.noreply.github.com>
Date: Wed, 25 Jun 2025 10:59:26 -0300
Subject: [PATCH] feat: add Audit360 Hoteis React app skeleton
---
README.md | 19 ++++++++--
favicon.svg | 5 +++
index.html | 13 +++++++
logo.svg | 5 +++
package.json | 24 +++++++++++++
src/App.tsx | 36 +++++++++++++++++++
src/auth.tsx | 73 +++++++++++++++++++++++++++++++++++++++
src/components/Header.tsx | 23 ++++++++++++
src/data/questions.ts | 19 ++++++++++
src/index.css | 62 +++++++++++++++++++++++++++++++++
src/main.tsx | 16 +++++++++
src/pages/AuditForm.tsx | 70 +++++++++++++++++++++++++++++++++++++
src/pages/Dashboard.tsx | 31 +++++++++++++++++
src/pages/Login.tsx | 45 ++++++++++++++++++++++++
src/pages/Reports.tsx | 47 +++++++++++++++++++++++++
src/types.ts | 12 +++++++
tsconfig.json | 21 +++++++++++
tsconfig.node.json | 9 +++++
vite.config.ts | 9 +++++
19 files changed, 537 insertions(+), 2 deletions(-)
create mode 100644 favicon.svg
create mode 100644 index.html
create mode 100644 logo.svg
create mode 100644 package.json
create mode 100644 src/App.tsx
create mode 100644 src/auth.tsx
create mode 100644 src/components/Header.tsx
create mode 100644 src/data/questions.ts
create mode 100644 src/index.css
create mode 100644 src/main.tsx
create mode 100644 src/pages/AuditForm.tsx
create mode 100644 src/pages/Dashboard.tsx
create mode 100644 src/pages/Login.tsx
create mode 100644 src/pages/Reports.tsx
create mode 100644 src/types.ts
create mode 100644 tsconfig.json
create mode 100644 tsconfig.node.json
create mode 100644 vite.config.ts
diff --git a/README.md b/README.md
index f2e32b4..645e828 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,17 @@
-# codex
-Repositorio codex open ai
+# Audit360 Hotéis
+
+Aplicativo web em React + TypeScript para auditorias de hotéis. O projeto foi criado manualmente com Vite e utiliza um backend simulado via `localStorage`.
+
+## Scripts
+
+- `npm run dev` inicia o servidor de desenvolvimento (requer dependências instaladas).
+- `npm run build` gera a versão de produção.
+
+## Funcionalidades
+
+- Autenticação simples de usuário (Supervisor, Gestora e Diretor).
+- Dashboard com listagem de auditorias e botão para iniciar novas auditorias.
+- Formulários de checklist com opções "Conforme", "Não conforme leve", "Não conforme grave" e upload de fotos.
+- Relatórios filtrados por unidade, data e responsável.
+
+As informações são salvas no `localStorage` do navegador e as imagens ficam codificadas em base64.
diff --git a/favicon.svg b/favicon.svg
new file mode 100644
index 0000000..f0adfba
--- /dev/null
+++ b/favicon.svg
@@ -0,0 +1,5 @@
+
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..2f9f924
--- /dev/null
+++ b/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Audit360 Hotéis
+
+
+
+
+
+
diff --git a/logo.svg b/logo.svg
new file mode 100644
index 0000000..f0adfba
--- /dev/null
+++ b/logo.svg
@@ -0,0 +1,5 @@
+
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..69ba02d
--- /dev/null
+++ b/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "audit360-hoteis",
+ "version": "0.1.0",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-router-dom": "^6.21.1"
+ },
+ "devDependencies": {
+ "@types/react": "^18.2.14",
+ "@types/react-dom": "^18.2.7",
+ "@types/react-router-dom": "^5.3.3",
+ "typescript": "^5.4.5",
+ "vite": "^5.2.0",
+ "@vitejs/plugin-react": "^4.0.3"
+ }
+}
diff --git a/src/App.tsx b/src/App.tsx
new file mode 100644
index 0000000..a0b49e5
--- /dev/null
+++ b/src/App.tsx
@@ -0,0 +1,36 @@
+import { Routes, Route, Navigate } from 'react-router-dom';
+import Login from './pages/Login';
+import Dashboard from './pages/Dashboard';
+import AuditForm from './pages/AuditForm';
+import Reports from './pages/Reports';
+import Header from './components/Header';
+import { useAuth } from './auth';
+
+function PrivateRoute({ children }: { children: JSX.Element }) {
+ const { user } = useAuth();
+ return user ? children : ;
+}
+
+export default function App() {
+ const { user } = useAuth();
+ return (
+ <>
+ {user && }
+
+ } />
+ }
+ />
+ }
+ />
+ }
+ />
+
+ >
+ );
+}
diff --git a/src/auth.tsx b/src/auth.tsx
new file mode 100644
index 0000000..b80a014
--- /dev/null
+++ b/src/auth.tsx
@@ -0,0 +1,73 @@
+import React, { createContext, useContext, useState, useEffect } from 'react';
+
+export type Role = 'Supervisor' | 'Gestora' | 'Diretor';
+
+export interface User {
+ name: string;
+ email: string;
+ role: Role;
+}
+
+interface AuthContextValue {
+ user: User | null;
+ login: (email: string, password: string) => Promise;
+ logout: () => void;
+}
+
+const AuthContext = createContext(null!);
+
+const fakeUsers: Record = {
+ 'supervisor@example.com': {
+ name: 'Supervisor',
+ email: 'supervisor@example.com',
+ role: 'Supervisor',
+ password: '1234'
+ },
+ 'gestora@example.com': {
+ name: 'Gestora',
+ email: 'gestora@example.com',
+ role: 'Gestora',
+ password: '1234'
+ },
+ 'diretor@example.com': {
+ name: 'Diretor',
+ email: 'diretor@example.com',
+ role: 'Diretor',
+ password: '1234'
+ }
+};
+
+export function AuthProvider({ children }: { children: React.ReactNode }) {
+ const [user, setUser] = useState(null);
+
+ useEffect(() => {
+ const stored = localStorage.getItem('audit360_user');
+ if (stored) setUser(JSON.parse(stored));
+ }, []);
+
+ const login = async (email: string, password: string) => {
+ const u = fakeUsers[email];
+ if (u && u.password === password) {
+ const { password: _, ...info } = u;
+ setUser(info);
+ localStorage.setItem('audit360_user', JSON.stringify(info));
+ return true;
+ }
+ return false;
+ };
+
+ const logout = () => {
+ setUser(null);
+ localStorage.removeItem('audit360_user');
+ };
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useAuth() {
+ return useContext(AuthContext);
+}
diff --git a/src/components/Header.tsx b/src/components/Header.tsx
new file mode 100644
index 0000000..18b8de5
--- /dev/null
+++ b/src/components/Header.tsx
@@ -0,0 +1,23 @@
+import { Link } from 'react-router-dom';
+import { useAuth } from '../auth';
+
+export default function Header() {
+ const { user, logout } = useAuth();
+ return (
+
+ );
+}
diff --git a/src/data/questions.ts b/src/data/questions.ts
new file mode 100644
index 0000000..9835c9a
--- /dev/null
+++ b/src/data/questions.ts
@@ -0,0 +1,19 @@
+export interface Question {
+ id: string;
+ text: string;
+}
+
+export const limpeza: Question[] = [
+ { id: 'l1', text: 'Quartos limpos' },
+ { id: 'l2', text: 'Áreas comuns higienizadas' }
+];
+
+export const operacao: Question[] = [
+ { id: 'o1', text: 'Atendimento cordial' },
+ { id: 'o2', text: 'Tempo de espera adequado' }
+];
+
+export const manutencao: Question[] = [
+ { id: 'm1', text: 'Equipamentos funcionando' },
+ { id: 'm2', text: 'Estrutura sem danos' }
+];
diff --git a/src/index.css b/src/index.css
new file mode 100644
index 0000000..4f316f6
--- /dev/null
+++ b/src/index.css
@@ -0,0 +1,62 @@
+body {
+ margin: 0;
+ font-family: Arial, Helvetica, sans-serif;
+ background-color: #f6f7f8;
+ color: #333;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+header {
+ background: #003366;
+ color: white;
+ padding: 0.5rem 1rem;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+header h1 {
+ font-size: 1.2rem;
+}
+
+button {
+ cursor: pointer;
+}
+
+nav a {
+ color: white;
+ margin-right: 1rem;
+ text-decoration: none;
+}
+
+.container {
+ padding: 1rem;
+}
+
+.audit-item {
+ padding: 0.5rem;
+ border-bottom: 1px solid #ccc;
+}
+
+form {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+}
+
+label {
+ font-weight: bold;
+}
+
+@media (min-width: 600px) {
+ header h1 {
+ font-size: 1.5rem;
+ }
+ .container {
+ max-width: 800px;
+ margin: 0 auto;
+ }
+}
diff --git a/src/main.tsx b/src/main.tsx
new file mode 100644
index 0000000..f7c043d
--- /dev/null
+++ b/src/main.tsx
@@ -0,0 +1,16 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import { BrowserRouter } from 'react-router-dom';
+import App from './App';
+import './index.css';
+import { AuthProvider } from './auth';
+
+ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
+
+
+
+
+
+
+
+);
diff --git a/src/pages/AuditForm.tsx b/src/pages/AuditForm.tsx
new file mode 100644
index 0000000..a5a8689
--- /dev/null
+++ b/src/pages/AuditForm.tsx
@@ -0,0 +1,70 @@
+import { FormEvent, useState } from 'react';
+import { useParams, useNavigate } from 'react-router-dom';
+import { limpeza, operacao, manutencao, Question } from '../data/questions';
+import { Audit } from '../types';
+import { useAuth } from '../auth';
+
+const options = ['Conforme', 'Não conforme leve', 'Não conforme grave'] as const;
+
+export default function AuditForm() {
+ const { type } = useParams();
+ const navigate = useNavigate();
+ const { user } = useAuth();
+ const questions: Question[] =
+ type === 'limpeza' ? limpeza : type === 'operacao' ? operacao : manutencao;
+
+ const [answers, setAnswers] = useState>({});
+ const [photos, setPhotos] = useState>({});
+
+ const handleFile = (q: string, files: FileList | null) => {
+ if (files && files[0]) {
+ const reader = new FileReader();
+ reader.onload = () => {
+ setPhotos((p) => ({ ...p, [q]: reader.result as string }));
+ };
+ reader.readAsDataURL(files[0]);
+ }
+ };
+
+ const handleSubmit = (e: FormEvent) => {
+ e.preventDefault();
+ const stored = localStorage.getItem('audits');
+ const audits: Audit[] = stored ? JSON.parse(stored) : [];
+ const audit: Audit = {
+ type: type || '',
+ date: new Date().toISOString().substring(0, 10),
+ responsible: user?.name || '',
+ answers: questions.map((q) => ({
+ questionId: q.id,
+ status: (answers[q.id] as any) || 'Conforme',
+ photo: photos[q.id]
+ }))
+ };
+ audits.push(audit);
+ localStorage.setItem('audits', JSON.stringify(audits));
+ navigate('/');
+ };
+
+ return (
+
+ );
+}
diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx
new file mode 100644
index 0000000..5e8bc9c
--- /dev/null
+++ b/src/pages/Dashboard.tsx
@@ -0,0 +1,31 @@
+import { Link } from 'react-router-dom';
+import { useEffect, useState } from 'react';
+import { Audit } from '../types';
+
+export default function Dashboard() {
+ const [audits, setAudits] = useState([]);
+
+ useEffect(() => {
+ const stored = localStorage.getItem('audits');
+ if (stored) setAudits(JSON.parse(stored));
+ }, []);
+
+ return (
+
+
Auditorias
+
+ Nova Auditoria Limpeza
+ {' | '}
+ Nova Auditoria Operação
+ {' | '}
+ Nova Auditoria Manutenção
+
+ {audits.map((a, i) => (
+
+ {a.type} - {a.date} - {a.responsible}
+
+ ))}
+ {audits.length === 0 &&
Nenhuma auditoria registrada.
}
+
+ );
+}
diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx
new file mode 100644
index 0000000..c6eebd7
--- /dev/null
+++ b/src/pages/Login.tsx
@@ -0,0 +1,45 @@
+import { useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { useAuth } from '../auth';
+
+export default function Login() {
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [error, setError] = useState('');
+ const navigate = useNavigate();
+ const { login } = useAuth();
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ const ok = await login(email, password);
+ if (ok) {
+ navigate('/');
+ } else {
+ setError('Credenciais inválidas');
+ }
+ };
+
+ return (
+
+
Login
+
+
+ );
+}
diff --git a/src/pages/Reports.tsx b/src/pages/Reports.tsx
new file mode 100644
index 0000000..aab2a5c
--- /dev/null
+++ b/src/pages/Reports.tsx
@@ -0,0 +1,47 @@
+import { useEffect, useState } from 'react';
+import { Audit } from '../types';
+
+export default function Reports() {
+ const [audits, setAudits] = useState([]);
+ const [unit, setUnit] = useState('');
+ const [responsible, setResponsible] = useState('');
+ const [date, setDate] = useState('');
+
+ useEffect(() => {
+ const stored = localStorage.getItem('audits');
+ if (stored) setAudits(JSON.parse(stored));
+ }, []);
+
+ const filtered = audits.filter((a) => {
+ return (
+ (unit ? a.type === unit : true) &&
+ (responsible ? a.responsible.includes(responsible) : true) &&
+ (date ? a.date === date : true)
+ );
+ });
+
+ return (
+
+
Relatórios
+
+
+
+
+ setResponsible(e.target.value)} />
+
+ setDate(e.target.value)} />
+
+ {filtered.map((a, i) => (
+
+ {a.type} - {a.date} - {a.responsible}
+
+ ))}
+ {filtered.length === 0 &&
Nenhum resultado.
}
+
+ );
+}
diff --git a/src/types.ts b/src/types.ts
new file mode 100644
index 0000000..89e84f8
--- /dev/null
+++ b/src/types.ts
@@ -0,0 +1,12 @@
+export interface AuditAnswer {
+ questionId: string;
+ status: 'Conforme' | 'Não conforme leve' | 'Não conforme grave';
+ photo?: string; // base64
+}
+
+export interface Audit {
+ type: string;
+ date: string;
+ responsible: string;
+ answers: AuditAnswer[];
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..3d0a51a
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "useDefineForClassFields": true,
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
+ "allowJs": false,
+ "skipLibCheck": true,
+ "esModuleInterop": false,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "module": "ESNext",
+ "moduleResolution": "Node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx"
+ },
+ "include": ["src"],
+ "references": [{ "path": "./tsconfig.node.json" }]
+}
diff --git a/tsconfig.node.json b/tsconfig.node.json
new file mode 100644
index 0000000..9d31e2a
--- /dev/null
+++ b/tsconfig.node.json
@@ -0,0 +1,9 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "module": "ESNext",
+ "moduleResolution": "Node",
+ "allowSyntheticDefaultImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..efe6335
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,9 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+
+export default defineConfig({
+ plugins: [react()],
+ server: {
+ port: 5173
+ }
+});