Merge pull request #1 from Rodribm10/codex/criar-aplicativo-web-audit360-hotéis
Implement Audit360 Hotéis app
This commit is contained in:
commit
ee4c7eb07a
19
README.md
19
README.md
@ -1,2 +1,17 @@
|
|||||||
# codex
|
# Audit360 Hotéis
|
||||||
Repositorio codex open ai
|
|
||||||
|
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.
|
||||||
|
|||||||
5
favicon.svg
Normal file
5
favicon.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||||
|
<rect width="100" height="100" rx="15" fill="#003366" />
|
||||||
|
<circle cx="50" cy="50" r="35" fill="white" />
|
||||||
|
<text x="50" y="57" font-size="30" text-anchor="middle" fill="#003366" font-family="Arial">360</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 282 B |
13
index.html
Normal file
13
index.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="pt-BR">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Audit360 Hotéis</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
5
logo.svg
Normal file
5
logo.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||||
|
<rect width="100" height="100" rx="15" fill="#003366" />
|
||||||
|
<circle cx="50" cy="50" r="35" fill="white" />
|
||||||
|
<text x="50" y="57" font-size="30" text-anchor="middle" fill="#003366" font-family="Arial">360</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 282 B |
24
package.json
Normal file
24
package.json
Normal file
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
36
src/App.tsx
Normal file
36
src/App.tsx
Normal file
@ -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 : <Navigate to="/login" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
const { user } = useAuth();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{user && <Header />}
|
||||||
|
<Routes>
|
||||||
|
<Route path="/login" element={<Login />} />
|
||||||
|
<Route
|
||||||
|
path="/"
|
||||||
|
element={<PrivateRoute><Dashboard /></PrivateRoute>}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/audit/:type"
|
||||||
|
element={<PrivateRoute><AuditForm /></PrivateRoute>}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/reports"
|
||||||
|
element={<PrivateRoute><Reports /></PrivateRoute>}
|
||||||
|
/>
|
||||||
|
</Routes>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
73
src/auth.tsx
Normal file
73
src/auth.tsx
Normal file
@ -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<boolean>;
|
||||||
|
logout: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AuthContext = createContext<AuthContextValue>(null!);
|
||||||
|
|
||||||
|
const fakeUsers: Record<string, User & { password: string }> = {
|
||||||
|
'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<User | null>(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 (
|
||||||
|
<AuthContext.Provider value={{ user, login, logout }}>
|
||||||
|
{children}
|
||||||
|
</AuthContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAuth() {
|
||||||
|
return useContext(AuthContext);
|
||||||
|
}
|
||||||
23
src/components/Header.tsx
Normal file
23
src/components/Header.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { useAuth } from '../auth';
|
||||||
|
|
||||||
|
export default function Header() {
|
||||||
|
const { user, logout } = useAuth();
|
||||||
|
return (
|
||||||
|
<header>
|
||||||
|
<h1>Audit360 Hotéis</h1>
|
||||||
|
<nav>
|
||||||
|
<Link to="/">Auditorias</Link>
|
||||||
|
<Link to="/reports">Relatórios</Link>
|
||||||
|
</nav>
|
||||||
|
<div>
|
||||||
|
{user && (
|
||||||
|
<>
|
||||||
|
<span>{user.name} - {user.email}</span>
|
||||||
|
<button onClick={logout}>Logout</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
19
src/data/questions.ts
Normal file
19
src/data/questions.ts
Normal file
@ -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' }
|
||||||
|
];
|
||||||
62
src/index.css
Normal file
62
src/index.css
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/main.tsx
Normal file
16
src/main.tsx
Normal file
@ -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(
|
||||||
|
<React.StrictMode>
|
||||||
|
<BrowserRouter>
|
||||||
|
<AuthProvider>
|
||||||
|
<App />
|
||||||
|
</AuthProvider>
|
||||||
|
</BrowserRouter>
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
70
src/pages/AuditForm.tsx
Normal file
70
src/pages/AuditForm.tsx
Normal file
@ -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<Record<string, string>>({});
|
||||||
|
const [photos, setPhotos] = useState<Record<string, string>>({});
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div className="container">
|
||||||
|
<h2>Auditoria {type}</h2>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
{questions.map((q) => (
|
||||||
|
<div key={q.id}>
|
||||||
|
<label>{q.text}</label>
|
||||||
|
<select
|
||||||
|
value={answers[q.id] || 'Conforme'}
|
||||||
|
onChange={(e) => setAnswers({ ...answers, [q.id]: e.target.value })}
|
||||||
|
>
|
||||||
|
{options.map((o) => (
|
||||||
|
<option key={o} value={o}>{o}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<input type="file" accept="image/*" onChange={(e) => handleFile(q.id, e.target.files)} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<button type="submit">Salvar</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
31
src/pages/Dashboard.tsx
Normal file
31
src/pages/Dashboard.tsx
Normal file
@ -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<Audit[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const stored = localStorage.getItem('audits');
|
||||||
|
if (stored) setAudits(JSON.parse(stored));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container">
|
||||||
|
<h2>Auditorias</h2>
|
||||||
|
<div style={{ marginBottom: '1rem' }}>
|
||||||
|
<Link to="/audit/limpeza">Nova Auditoria Limpeza</Link>
|
||||||
|
{' | '}
|
||||||
|
<Link to="/audit/operacao">Nova Auditoria Operação</Link>
|
||||||
|
{' | '}
|
||||||
|
<Link to="/audit/manutencao">Nova Auditoria Manutenção</Link>
|
||||||
|
</div>
|
||||||
|
{audits.map((a, i) => (
|
||||||
|
<div key={i} className="audit-item">
|
||||||
|
<strong>{a.type}</strong> - {a.date} - {a.responsible}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{audits.length === 0 && <p>Nenhuma auditoria registrada.</p>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
45
src/pages/Login.tsx
Normal file
45
src/pages/Login.tsx
Normal file
@ -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 (
|
||||||
|
<div className="container">
|
||||||
|
<h2>Login</h2>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<label>E-mail</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<label>Senha</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
{error && <span style={{ color: 'red' }}>{error}</span>}
|
||||||
|
<button type="submit">Entrar</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
47
src/pages/Reports.tsx
Normal file
47
src/pages/Reports.tsx
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Audit } from '../types';
|
||||||
|
|
||||||
|
export default function Reports() {
|
||||||
|
const [audits, setAudits] = useState<Audit[]>([]);
|
||||||
|
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 (
|
||||||
|
<div className="container">
|
||||||
|
<h2>Relatórios</h2>
|
||||||
|
<div style={{ marginBottom: '1rem' }}>
|
||||||
|
<label>Unidade</label>
|
||||||
|
<select value={unit} onChange={(e) => setUnit(e.target.value)}>
|
||||||
|
<option value="">Todas</option>
|
||||||
|
<option value="limpeza">Limpeza</option>
|
||||||
|
<option value="operacao">Operação</option>
|
||||||
|
<option value="manutencao">Manutenção</option>
|
||||||
|
</select>
|
||||||
|
<label>Responsável</label>
|
||||||
|
<input value={responsible} onChange={(e) => setResponsible(e.target.value)} />
|
||||||
|
<label>Data</label>
|
||||||
|
<input type="date" value={date} onChange={(e) => setDate(e.target.value)} />
|
||||||
|
</div>
|
||||||
|
{filtered.map((a, i) => (
|
||||||
|
<div key={i} className="audit-item">
|
||||||
|
<strong>{a.type}</strong> - {a.date} - {a.responsible}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{filtered.length === 0 && <p>Nenhum resultado.</p>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
12
src/types.ts
Normal file
12
src/types.ts
Normal file
@ -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[];
|
||||||
|
}
|
||||||
21
tsconfig.json
Normal file
21
tsconfig.json
Normal file
@ -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" }]
|
||||||
|
}
|
||||||
9
tsconfig.node.json
Normal file
9
tsconfig.node.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
9
vite.config.ts
Normal file
9
vite.config.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
server: {
|
||||||
|
port: 5173
|
||||||
|
}
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user