diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3a5c1d0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+node_modules
+dist
+.env
+.env.local
+*.log
+.DS_Store
diff --git a/README.md b/README.md
index 6918e7f..7b41db9 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,55 @@
-# jira-ai-fixer-portal
+# JIRA AI Fixer Portal
-JIRA AI Fixer Portal - React Dashboard for intelligent support case resolution
\ No newline at end of file
+React dashboard for the JIRA AI Fixer - Intelligent Support Case Resolution system.
+
+## Features
+
+- 📊 **Dashboard** - Real-time stats and recent issues
+- 🎫 **Issues** - Browse and filter analyzed issues
+- 📁 **Repositories** - Manage connected code repositories
+- ⚙️ **Settings** - Configure integrations and AI settings
+
+## Tech Stack
+
+- React 18 + TypeScript
+- Vite
+- TailwindCSS
+- React Query
+- React Router
+
+## Development
+
+```bash
+# Install dependencies
+npm install
+
+# Start dev server
+npm run dev
+
+# Build for production
+npm run build
+```
+
+## Environment Variables
+
+```env
+VITE_API_URL=https://jira-fixer.startdata.com.br/api
+```
+
+## Integrations
+
+### Issue Trackers
+- TicketHub (Active)
+- JIRA (Ready)
+- ServiceNow (Ready)
+- Azure DevOps (Ready)
+
+### Code Repositories
+- Gitea (Active)
+- GitHub (Ready)
+- GitLab (Ready)
+- Bitbucket (Ready)
+
+## License
+
+MIT
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..b23943d
--- /dev/null
+++ b/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ JIRA AI Fixer Portal
+
+
+
+
+
+
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..d556e5f
--- /dev/null
+++ b/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "jira-ai-fixer-portal",
+ "version": "1.0.0",
+ "description": "JIRA AI Fixer Portal - React Dashboard",
+ "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.22.0",
+ "@tanstack/react-query": "^5.17.0",
+ "axios": "^1.6.0",
+ "date-fns": "^3.3.0",
+ "recharts": "^2.12.0"
+ },
+ "devDependencies": {
+ "@types/react": "^18.2.48",
+ "@types/react-dom": "^18.2.18",
+ "@vitejs/plugin-react": "^4.2.1",
+ "autoprefixer": "^10.4.17",
+ "postcss": "^8.4.33",
+ "tailwindcss": "^3.4.1",
+ "typescript": "^5.3.3",
+ "vite": "^5.0.12"
+ }
+}
diff --git a/postcss.config.js b/postcss.config.js
new file mode 100644
index 0000000..2e7af2b
--- /dev/null
+++ b/postcss.config.js
@@ -0,0 +1,6 @@
+export default {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+}
diff --git a/public/favicon.svg b/public/favicon.svg
new file mode 100644
index 0000000..083f157
--- /dev/null
+++ b/public/favicon.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/App.tsx b/src/App.tsx
new file mode 100644
index 0000000..e192f44
--- /dev/null
+++ b/src/App.tsx
@@ -0,0 +1,21 @@
+import { Routes, Route } from 'react-router-dom'
+import Layout from './components/Layout'
+import Dashboard from './pages/Dashboard'
+import Issues from './pages/Issues'
+import IssueDetail from './pages/IssueDetail'
+import Repositories from './pages/Repositories'
+import Settings from './pages/Settings'
+
+export default function App() {
+ return (
+
+ }>
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+ )
+}
diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx
new file mode 100644
index 0000000..9cbe88d
--- /dev/null
+++ b/src/components/Layout.tsx
@@ -0,0 +1,60 @@
+import { Outlet, NavLink } from 'react-router-dom'
+
+const navItems = [
+ { to: '/', label: 'Dashboard', icon: '📊' },
+ { to: '/issues', label: 'Issues', icon: '🎫' },
+ { to: '/repositories', label: 'Repositories', icon: '📁' },
+ { to: '/settings', label: 'Settings', icon: '⚙️' },
+]
+
+export default function Layout() {
+ return (
+
+ {/* Sidebar */}
+
+
+ {/* Main content */}
+
+
+
+
+ )
+}
diff --git a/src/index.css b/src/index.css
new file mode 100644
index 0000000..ef59836
--- /dev/null
+++ b/src/index.css
@@ -0,0 +1,7 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+body {
+ font-family: 'Inter', system-ui, sans-serif;
+}
diff --git a/src/main.tsx b/src/main.tsx
new file mode 100644
index 0000000..f4da9f2
--- /dev/null
+++ b/src/main.tsx
@@ -0,0 +1,25 @@
+import React from 'react'
+import ReactDOM from 'react-dom/client'
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
+import { BrowserRouter } from 'react-router-dom'
+import App from './App'
+import './index.css'
+
+const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ refetchOnWindowFocus: false,
+ retry: 1,
+ },
+ },
+})
+
+ReactDOM.createRoot(document.getElementById('root')!).render(
+
+
+
+
+
+
+ ,
+)
diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx
new file mode 100644
index 0000000..94693df
--- /dev/null
+++ b/src/pages/Dashboard.tsx
@@ -0,0 +1,137 @@
+import { useQuery } from '@tanstack/react-query'
+import { issuesApi } from '../services/api'
+import { Link } from 'react-router-dom'
+
+export default function Dashboard() {
+ const { data: stats } = useQuery({
+ queryKey: ['stats'],
+ queryFn: issuesApi.getStats,
+ refetchInterval: 10000,
+ })
+
+ const { data: issues } = useQuery({
+ queryKey: ['issues'],
+ queryFn: () => issuesApi.list(),
+ refetchInterval: 10000,
+ })
+
+ const recentIssues = issues?.slice(0, 5) || []
+
+ return (
+
+
Dashboard
+
+ {/* Stats Grid */}
+
+
+
+
+
+
+
+ {/* Recent Issues */}
+
+
+
Recent Issues
+
+ View all →
+
+
+
+ {recentIssues.length === 0 ? (
+
No issues yet
+ ) : (
+ recentIssues.map(issue => (
+
+
+
+
+
+ {issue.external_key || `#${issue.id}`}
+
+
+
+
{issue.title}
+
+ {issue.confidence && (
+
+ {Math.round(issue.confidence * 100)}%
+
+ )}
+
+
+ ))
+ )}
+
+
+
+ )
+}
+
+function StatCard({ title, value, icon, color }: {
+ title: string
+ value: number | string
+ icon: string
+ color: 'blue' | 'green' | 'purple' | 'yellow'
+}) {
+ const colors = {
+ blue: 'bg-blue-500/20 text-blue-400',
+ green: 'bg-green-500/20 text-green-400',
+ purple: 'bg-purple-500/20 text-purple-400',
+ yellow: 'bg-yellow-500/20 text-yellow-400',
+ }
+
+ return (
+
+
+
+
{title}
+
+ {value}
+
+
+
+ {icon}
+
+
+
+ )
+}
+
+function StatusBadge({ status }: { status: string }) {
+ const styles = {
+ analyzed: 'bg-green-500/20 text-green-400',
+ pending: 'bg-yellow-500/20 text-yellow-400',
+ error: 'bg-red-500/20 text-red-400',
+ }
+
+ return (
+
+ {status}
+
+ )
+}
diff --git a/src/pages/IssueDetail.tsx b/src/pages/IssueDetail.tsx
new file mode 100644
index 0000000..9ae015d
--- /dev/null
+++ b/src/pages/IssueDetail.tsx
@@ -0,0 +1,139 @@
+import { useParams, Link } from 'react-router-dom'
+import { useQuery } from '@tanstack/react-query'
+import { issuesApi } from '../services/api'
+
+export default function IssueDetail() {
+ const { id } = useParams()
+
+ const { data: issue, isLoading, error } = useQuery({
+ queryKey: ['issue', id],
+ queryFn: () => issuesApi.get(Number(id)),
+ enabled: !!id,
+ })
+
+ if (isLoading) {
+ return (
+
+ )
+ }
+
+ if (error || !issue) {
+ return (
+
+
Issue not found
+
+ ← Back to issues
+
+
+ )
+ }
+
+ const statusStyles = {
+ analyzed: 'bg-green-500/20 text-green-400 border-green-500/30',
+ pending: 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30',
+ error: 'bg-red-500/20 text-red-400 border-red-500/30',
+ }
+
+ let affectedFiles: string[] = []
+ try {
+ affectedFiles = JSON.parse(issue.affected_files || '[]')
+ } catch (e) {}
+
+ return (
+
+ {/* Header */}
+
+
+ ← Back to issues
+
+
+
+
+
+
+
+
+ {issue.external_key || `#${issue.id}`}
+
+
+ {issue.status}
+
+
+
{issue.title}
+
Source: {issue.source}
+
+
+ {issue.confidence && (
+
+
Confidence
+
+ {Math.round(issue.confidence * 100)}%
+
+
+ )}
+
+
+
+ {/* Description */}
+
+
Description
+
+ {issue.description}
+
+
+
+ {/* Analysis */}
+ {issue.analysis && (
+
+
🔍 Analysis
+
+ {issue.analysis}
+
+
+ )}
+
+ {/* Affected Files */}
+ {affectedFiles.length > 0 && (
+
+
📁 Affected Files
+
+ {affectedFiles.map((file, i) => (
+
+ {file}
+
+ ))}
+
+
+ )}
+
+ {/* Suggested Fix */}
+ {issue.suggested_fix && (
+
+
🔧 Suggested Fix
+
+ {issue.suggested_fix}
+
+
+ )}
+
+ {/* Metadata */}
+
+
Details
+
+
+ Created:
+ {new Date(issue.created_at).toLocaleString()}
+
+ {issue.analyzed_at && (
+
+ Analyzed:
+ {new Date(issue.analyzed_at).toLocaleString()}
+
+ )}
+
+
+
+ )
+}
diff --git a/src/pages/Issues.tsx b/src/pages/Issues.tsx
new file mode 100644
index 0000000..b2d2e39
--- /dev/null
+++ b/src/pages/Issues.tsx
@@ -0,0 +1,90 @@
+import { useState } from 'react'
+import { useQuery } from '@tanstack/react-query'
+import { Link } from 'react-router-dom'
+import { issuesApi, Issue } from '../services/api'
+
+export default function Issues() {
+ const [filter, setFilter] = useState('')
+
+ const { data: issues, isLoading } = useQuery({
+ queryKey: ['issues', filter],
+ queryFn: () => issuesApi.list(filter || undefined),
+ refetchInterval: 10000,
+ })
+
+ return (
+
+
+
Issues
+
+
+
+
+ {isLoading ? (
+
Loading...
+ ) : !issues?.length ? (
+
No issues found
+ ) : (
+
+ {issues.map(issue => (
+
+ ))}
+
+ )}
+
+
+ )
+}
+
+function IssueRow({ issue }: { issue: Issue }) {
+ const statusStyles = {
+ analyzed: 'bg-green-500/20 text-green-400',
+ pending: 'bg-yellow-500/20 text-yellow-400',
+ error: 'bg-red-500/20 text-red-400',
+ }
+
+ return (
+
+
+
+
+
+ {issue.external_key || `#${issue.id}`}
+
+
+ {issue.status}
+
+ {issue.source}
+
+
{issue.title}
+
{issue.description}
+
+ {issue.confidence && (
+
+
+
+ {Math.round(issue.confidence * 100)}%
+
+
+ )}
+
+
+ )
+}
diff --git a/src/pages/Repositories.tsx b/src/pages/Repositories.tsx
new file mode 100644
index 0000000..aaf8206
--- /dev/null
+++ b/src/pages/Repositories.tsx
@@ -0,0 +1,118 @@
+export default function Repositories() {
+ const repos = [
+ {
+ name: 'cobol-sample-app',
+ url: 'https://gitea.startdata.com.br/startdata/cobol-sample-app',
+ files: 4,
+ language: 'COBOL',
+ status: 'indexed',
+ },
+ ]
+
+ return (
+
+
+
Repositories
+
+
+
+
+ {repos.map((repo, i) => (
+
+
+
+ 📁
+
+
+
{repo.name}
+
{repo.url}
+
+
+
+
+
+
{repo.language}
+
Language
+
+
+ {repo.status}
+
+
+
+ ))}
+
+
+ {/* Integrations */}
+
Integrations
+
+
+
+
+
+
+
+
+ )
+}
+
+function IntegrationCard({ name, icon, status, description }: {
+ name: string
+ icon: string
+ status: 'connected' | 'available'
+ description: string
+}) {
+ return (
+
+
+
+ {icon}
+
{name}
+
+
+ {status}
+
+
+
{description}
+ {status === 'available' && (
+
+ )}
+
+ )
+}
diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx
new file mode 100644
index 0000000..3089da0
--- /dev/null
+++ b/src/pages/Settings.tsx
@@ -0,0 +1,119 @@
+export default function Settings() {
+ return (
+
+
Settings
+
+ {/* Webhook Endpoints */}
+
+
Webhook Endpoints
+
+
+
+
+
+
+
+ {/* Issue Trackers */}
+
+
Issue Tracker Integrations
+
+
+
+
+
+
+
+
+ {/* AI Settings */}
+
+
AI Configuration
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0%
+ 70%
+ 100%
+
+
+
+
+
+ )
+}
+
+function EndpointRow({ method, path, description }: {
+ method: string
+ path: string
+ description: string
+}) {
+ return (
+
+
+ {method}
+
+ {path}
+ {description}
+
+
+ )
+}
+
+function TrackerCard({ name, icon, status }: {
+ name: string
+ icon: string
+ status: 'active' | 'ready'
+}) {
+ return (
+
+
+ {icon}
+ {name}
+
+
+ {status}
+
+
+ )
+}
diff --git a/src/services/api.ts b/src/services/api.ts
new file mode 100644
index 0000000..c101cf2
--- /dev/null
+++ b/src/services/api.ts
@@ -0,0 +1,59 @@
+import axios from 'axios'
+
+const API_URL = import.meta.env.VITE_API_URL || '/api'
+
+export const api = axios.create({
+ baseURL: API_URL,
+})
+
+export interface Issue {
+ id: number
+ external_key: string
+ source: string
+ title: string
+ description: string
+ status: 'pending' | 'analyzed' | 'error'
+ analysis?: string
+ affected_files?: string
+ suggested_fix?: string
+ confidence?: number
+ created_at: string
+ analyzed_at?: string
+}
+
+export interface Stats {
+ total: number
+ analyzed: number
+ pending: number
+ error: number
+ prs_created: number
+ avg_confidence: number
+}
+
+export const issuesApi = {
+ list: async (status?: string): Promise => {
+ const params = status ? { status } : {}
+ const { data } = await api.get('/issues', { params })
+ return data
+ },
+
+ get: async (id: number): Promise => {
+ const { data } = await api.get(`/issues/${id}`)
+ return data
+ },
+
+ getStats: async (): Promise => {
+ const issues = await issuesApi.list()
+ const analyzed = issues.filter(i => i.status === 'analyzed')
+ return {
+ total: issues.length,
+ analyzed: analyzed.length,
+ pending: issues.filter(i => i.status === 'pending').length,
+ error: issues.filter(i => i.status === 'error').length,
+ prs_created: analyzed.filter(i => i.suggested_fix).length,
+ avg_confidence: analyzed.length
+ ? Math.round(analyzed.reduce((a, i) => a + (i.confidence || 0), 0) / analyzed.length * 100)
+ : 0
+ }
+ }
+}
diff --git a/tailwind.config.js b/tailwind.config.js
new file mode 100644
index 0000000..7141e45
--- /dev/null
+++ b/tailwind.config.js
@@ -0,0 +1,8 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+ content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
+ theme: {
+ extend: {},
+ },
+ plugins: [],
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..3934b8f
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["src"],
+ "references": [{ "path": "./tsconfig.node.json" }]
+}
diff --git a/tsconfig.node.json b/tsconfig.node.json
new file mode 100644
index 0000000..42872c5
--- /dev/null
+++ b/tsconfig.node.json
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "skipLibCheck": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "allowSyntheticDefaultImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..6318ceb
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,11 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+
+export default defineConfig({
+ plugins: [react()],
+ server: {
+ proxy: {
+ '/api': 'http://localhost:8000'
+ }
+ }
+})