diff --git a/src/App.tsx b/src/App.tsx
index e192f44..00c3774 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -4,6 +4,10 @@ import Dashboard from './pages/Dashboard'
import Issues from './pages/Issues'
import IssueDetail from './pages/IssueDetail'
import Repositories from './pages/Repositories'
+import Integrations from './pages/Integrations'
+import Rules from './pages/Rules'
+import Analytics from './pages/Analytics'
+import Team from './pages/Team'
import Settings from './pages/Settings'
export default function App() {
@@ -11,9 +15,13 @@ export default function App() {
}>
} />
+ } />
} />
} />
} />
+ } />
+ } />
+ } />
} />
diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx
index 9cbe88d..6b98119 100644
--- a/src/components/Layout.tsx
+++ b/src/components/Layout.tsx
@@ -1,60 +1,137 @@
-import { Outlet, NavLink } from 'react-router-dom'
+import { Outlet, NavLink, useLocation } from 'react-router-dom'
+import { useState } from 'react'
-const navItems = [
- { to: '/', label: 'Dashboard', icon: 'π' },
- { to: '/issues', label: 'Issues', icon: 'π«' },
- { to: '/repositories', label: 'Repositories', icon: 'π' },
- { to: '/settings', label: 'Settings', icon: 'βοΈ' },
+const navSections = [
+ {
+ title: 'Overview',
+ items: [
+ { to: '/', label: 'Dashboard', icon: 'π' },
+ { to: '/analytics', label: 'Analytics', icon: 'π' },
+ ],
+ },
+ {
+ title: 'Work',
+ items: [
+ { to: '/issues', label: 'Issues', icon: 'π«' },
+ { to: '/repositories', label: 'Repositories', icon: 'π' },
+ ],
+ },
+ {
+ title: 'Configuration',
+ items: [
+ { to: '/integrations', label: 'Integrations', icon: 'π' },
+ { to: '/rules', label: 'Automation', icon: 'β‘' },
+ { to: '/team', label: 'Team', icon: 'π₯' },
+ { to: '/settings', label: 'Settings', icon: 'βοΈ' },
+ ],
+ },
]
export default function Layout() {
+ const [collapsed, setCollapsed] = useState(false)
+ const location = useLocation()
+
return (
{/* Sidebar */}
-
)
}
diff --git a/src/components/ui/Badge.tsx b/src/components/ui/Badge.tsx
new file mode 100644
index 0000000..d899f9d
--- /dev/null
+++ b/src/components/ui/Badge.tsx
@@ -0,0 +1,28 @@
+import { ReactNode } from 'react'
+
+interface BadgeProps {
+ children: ReactNode
+ variant?: 'success' | 'warning' | 'error' | 'info' | 'default'
+ size?: 'sm' | 'md'
+}
+
+export function Badge({ children, variant = 'default', size = 'sm' }: BadgeProps) {
+ const variants = {
+ success: 'bg-green-500/20 text-green-400 border-green-500/30',
+ warning: 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30',
+ error: 'bg-red-500/20 text-red-400 border-red-500/30',
+ info: 'bg-blue-500/20 text-blue-400 border-blue-500/30',
+ default: 'bg-gray-500/20 text-gray-400 border-gray-500/30',
+ }
+
+ const sizes = {
+ sm: 'px-2 py-0.5 text-xs',
+ md: 'px-3 py-1 text-sm',
+ }
+
+ return (
+
+ {children}
+
+ )
+}
diff --git a/src/components/ui/Button.tsx b/src/components/ui/Button.tsx
new file mode 100644
index 0000000..faa6886
--- /dev/null
+++ b/src/components/ui/Button.tsx
@@ -0,0 +1,50 @@
+import { ButtonHTMLAttributes, ReactNode } from 'react'
+
+interface ButtonProps extends ButtonHTMLAttributes {
+ variant?: 'primary' | 'secondary' | 'danger' | 'ghost'
+ size?: 'sm' | 'md' | 'lg'
+ children: ReactNode
+ loading?: boolean
+}
+
+export function Button({
+ variant = 'primary',
+ size = 'md',
+ children,
+ loading,
+ className = '',
+ disabled,
+ ...props
+}: ButtonProps) {
+ const variants = {
+ primary: 'bg-blue-600 hover:bg-blue-700 text-white',
+ secondary: 'bg-gray-700 hover:bg-gray-600 text-white',
+ danger: 'bg-red-600 hover:bg-red-700 text-white',
+ ghost: 'bg-transparent hover:bg-gray-700 text-gray-300',
+ }
+
+ const sizes = {
+ sm: 'px-3 py-1.5 text-sm',
+ md: 'px-4 py-2',
+ lg: 'px-6 py-3 text-lg',
+ }
+
+ return (
+
+ )
+}
diff --git a/src/components/ui/Card.tsx b/src/components/ui/Card.tsx
new file mode 100644
index 0000000..e804876
--- /dev/null
+++ b/src/components/ui/Card.tsx
@@ -0,0 +1,34 @@
+import { ReactNode } from 'react'
+
+interface CardProps {
+ children: ReactNode
+ className?: string
+ padding?: 'none' | 'sm' | 'md' | 'lg'
+}
+
+export function Card({ children, className = '', padding = 'md' }: CardProps) {
+ const paddings = {
+ none: '',
+ sm: 'p-4',
+ md: 'p-6',
+ lg: 'p-8',
+ }
+
+ return (
+
+ {children}
+
+ )
+}
+
+export function CardHeader({ children, className = '' }: { children: ReactNode, className?: string }) {
+ return (
+
+ {children}
+
+ )
+}
+
+export function CardContent({ children, className = '' }: { children: ReactNode, className?: string }) {
+ return {children}
+}
diff --git a/src/components/ui/Input.tsx b/src/components/ui/Input.tsx
new file mode 100644
index 0000000..5ee7214
--- /dev/null
+++ b/src/components/ui/Input.tsx
@@ -0,0 +1,35 @@
+import { InputHTMLAttributes, forwardRef } from 'react'
+
+interface InputProps extends InputHTMLAttributes {
+ label?: string
+ error?: string
+ hint?: string
+}
+
+export const Input = forwardRef(
+ ({ label, error, hint, className = '', ...props }, ref) => {
+ return (
+
+ {label && (
+
+ )}
+
+ {hint && !error && (
+
{hint}
+ )}
+ {error && (
+
{error}
+ )}
+
+ )
+ }
+)
diff --git a/src/components/ui/Modal.tsx b/src/components/ui/Modal.tsx
new file mode 100644
index 0000000..877cee5
--- /dev/null
+++ b/src/components/ui/Modal.tsx
@@ -0,0 +1,50 @@
+import { ReactNode, useEffect } from 'react'
+
+interface ModalProps {
+ open: boolean
+ onClose: () => void
+ title: string
+ children: ReactNode
+ size?: 'sm' | 'md' | 'lg' | 'xl'
+}
+
+export function Modal({ open, onClose, title, children, size = 'md' }: ModalProps) {
+ useEffect(() => {
+ if (open) {
+ document.body.style.overflow = 'hidden'
+ } else {
+ document.body.style.overflow = ''
+ }
+ return () => {
+ document.body.style.overflow = ''
+ }
+ }, [open])
+
+ if (!open) return null
+
+ const sizes = {
+ sm: 'max-w-md',
+ md: 'max-w-lg',
+ lg: 'max-w-2xl',
+ xl: 'max-w-4xl',
+ }
+
+ return (
+
+ )
+}
diff --git a/src/components/ui/Select.tsx b/src/components/ui/Select.tsx
new file mode 100644
index 0000000..c3a64de
--- /dev/null
+++ b/src/components/ui/Select.tsx
@@ -0,0 +1,36 @@
+import { SelectHTMLAttributes, forwardRef } from 'react'
+
+interface SelectProps extends SelectHTMLAttributes {
+ label?: string
+ error?: string
+ options: { value: string; label: string }[]
+}
+
+export const Select = forwardRef(
+ ({ label, error, options, className = '', ...props }, ref) => {
+ return (
+
+ {label && (
+
+ )}
+
+ {error && (
+
{error}
+ )}
+
+ )
+ }
+)
diff --git a/src/components/ui/Tabs.tsx b/src/components/ui/Tabs.tsx
new file mode 100644
index 0000000..8c17352
--- /dev/null
+++ b/src/components/ui/Tabs.tsx
@@ -0,0 +1,53 @@
+import { ReactNode, useState, createContext, useContext } from 'react'
+
+interface TabsContextValue {
+ activeTab: string
+ setActiveTab: (tab: string) => void
+}
+
+const TabsContext = createContext(null)
+
+export function Tabs({ defaultValue, children }: { defaultValue: string; children: ReactNode }) {
+ const [activeTab, setActiveTab] = useState(defaultValue)
+
+ return (
+
+ {children}
+
+ )
+}
+
+export function TabsList({ children }: { children: ReactNode }) {
+ return (
+
+ {children}
+
+ )
+}
+
+export function TabsTrigger({ value, children }: { value: string; children: ReactNode }) {
+ const ctx = useContext(TabsContext)
+ if (!ctx) return null
+
+ const isActive = ctx.activeTab === value
+
+ return (
+
+ )
+}
+
+export function TabsContent({ value, children }: { value: string; children: ReactNode }) {
+ const ctx = useContext(TabsContext)
+ if (!ctx || ctx.activeTab !== value) return null
+
+ return {children}
+}
diff --git a/src/components/ui/index.ts b/src/components/ui/index.ts
new file mode 100644
index 0000000..279f54b
--- /dev/null
+++ b/src/components/ui/index.ts
@@ -0,0 +1,7 @@
+export * from './Card'
+export * from './Button'
+export * from './Input'
+export * from './Select'
+export * from './Badge'
+export * from './Modal'
+export * from './Tabs'
diff --git a/src/pages/Analytics.tsx b/src/pages/Analytics.tsx
new file mode 100644
index 0000000..9cf6700
--- /dev/null
+++ b/src/pages/Analytics.tsx
@@ -0,0 +1,182 @@
+import { Card } from '../components/ui/Card'
+import { Select } from '../components/ui/Select'
+
+export default function Analytics() {
+ return (
+
+
+
+
Analytics
+
Performance metrics and insights
+
+
+
+
+ {/* KPI Cards */}
+
+
+
+
+
+
+
+ {/* Charts */}
+
+
+ Issues Over Time
+
+ {[40, 65, 45, 80, 55, 90, 70, 85, 60, 95, 75, 88].map((h, i) => (
+
+
+
{['J','F','M','A','M','J','J','A','S','O','N','D'][i]}
+
+ ))}
+
+
+
+
+ Resolution by Category
+
+ {[
+ { label: 'Data Truncation', value: 35, color: 'blue' },
+ { label: 'Missing Validation', value: 28, color: 'green' },
+ { label: 'Logic Error', value: 20, color: 'purple' },
+ { label: 'Performance', value: 12, color: 'yellow' },
+ { label: 'Other', value: 5, color: 'gray' },
+ ].map(item => (
+
+
+ {item.label}
+ {item.value}%
+
+
+
+ ))}
+
+
+
+
+ {/* Tables */}
+
+
+ Top Affected Modules
+
+
+
+ | Module |
+ Issues |
+ Fix Rate |
+
+
+
+ {[
+ { name: 'AUTH.CBL', issues: 23, rate: '96%' },
+ { name: 'TRANS.CBL', issues: 18, rate: '94%' },
+ { name: 'BATCH.CBL', issues: 15, rate: '93%' },
+ { name: 'REPORT.CBL', issues: 12, rate: '92%' },
+ { name: 'VALID.CBL', issues: 9, rate: '100%' },
+ ].map(row => (
+
+ | {row.name} |
+ {row.issues} |
+ {row.rate} |
+
+ ))}
+
+
+
+
+
+ Recent Activity
+
+ {[
+ { action: 'PR merged', issue: 'SUPP-5', time: '2 min ago', icon: 'β
' },
+ { action: 'Analysis complete', issue: 'SUPP-4', time: '15 min ago', icon: 'π' },
+ { action: 'New issue', issue: 'SUPP-3', time: '1 hour ago', icon: 'π«' },
+ { action: 'Rule triggered', issue: 'Auto-analyze', time: '2 hours ago', icon: 'β‘' },
+ { action: 'Integration sync', issue: 'Gitea', time: '3 hours ago', icon: 'π' },
+ ].map((item, i) => (
+
+
{item.icon}
+
+
{item.action}
+
{item.issue}
+
+
{item.time}
+
+ ))}
+
+
+
+
+ )
+}
+
+function KPICard({ title, value, change, trend, icon }: {
+ title: string
+ value: string
+ change: string
+ trend: 'up' | 'down'
+ icon: string
+}) {
+ return (
+
+
+
+
{title}
+
{value}
+
+ {change} vs last period
+
+
+
{icon}
+
+
+ )
+}
diff --git a/src/pages/Integrations.tsx b/src/pages/Integrations.tsx
new file mode 100644
index 0000000..ec0f6ed
--- /dev/null
+++ b/src/pages/Integrations.tsx
@@ -0,0 +1,326 @@
+import { useState } from 'react'
+import { Card, CardHeader, CardContent } from '../components/ui/Card'
+import { Button } from '../components/ui/Button'
+import { Input } from '../components/ui/Input'
+import { Select } from '../components/ui/Select'
+import { Badge } from '../components/ui/Badge'
+import { Modal } from '../components/ui/Modal'
+import { Tabs, TabsList, TabsTrigger, TabsContent } from '../components/ui/Tabs'
+
+interface Integration {
+ id: string
+ name: string
+ type: 'issue_tracker' | 'repository'
+ provider: string
+ status: 'connected' | 'error' | 'pending'
+ config: Record
+ lastSync?: string
+}
+
+const mockIntegrations: Integration[] = [
+ {
+ id: '1',
+ name: 'TicketHub Production',
+ type: 'issue_tracker',
+ provider: 'tickethub',
+ status: 'connected',
+ config: { url: 'https://tickethub.startdata.com.br', projectKey: 'SUPP' },
+ lastSync: '2026-02-18T18:00:00Z',
+ },
+ {
+ id: '2',
+ name: 'Gitea StartData',
+ type: 'repository',
+ provider: 'gitea',
+ status: 'connected',
+ config: { url: 'https://gitea.startdata.com.br', org: 'startdata' },
+ lastSync: '2026-02-18T17:30:00Z',
+ },
+]
+
+const providers = {
+ issue_tracker: [
+ { id: 'tickethub', name: 'TicketHub', icon: 'π«', color: 'blue' },
+ { id: 'jira', name: 'Jira', icon: 'π΅', color: 'blue' },
+ { id: 'servicenow', name: 'ServiceNow', icon: 'π’', color: 'green' },
+ { id: 'azure_devops', name: 'Azure DevOps', icon: 'π·', color: 'cyan' },
+ { id: 'zendesk', name: 'Zendesk', icon: 'π¬', color: 'gray' },
+ ],
+ repository: [
+ { id: 'gitea', name: 'Gitea', icon: 'β', color: 'green' },
+ { id: 'github', name: 'GitHub', icon: 'π', color: 'gray' },
+ { id: 'gitlab', name: 'GitLab', icon: 'π¦', color: 'orange' },
+ { id: 'bitbucket', name: 'Bitbucket', icon: 'πͺ£', color: 'blue' },
+ { id: 'azure_repos', name: 'Azure Repos', icon: 'π·', color: 'cyan' },
+ ],
+}
+
+export default function Integrations() {
+ const [integrations] = useState(mockIntegrations)
+ const [showAddModal, setShowAddModal] = useState(false)
+ const [addType, setAddType] = useState<'issue_tracker' | 'repository'>('issue_tracker')
+ const [selectedProvider, setSelectedProvider] = useState('')
+
+ return (
+
+
+
+
Integrations
+
Connect your issue trackers and code repositories
+
+
+
+
+
+
+ All ({integrations.length})
+ Issue Trackers ({integrations.filter(i => i.type === 'issue_tracker').length})
+ Repositories ({integrations.filter(i => i.type === 'repository').length})
+
+
+
+
+
+
+ i.type === 'issue_tracker')} />
+
+
+ i.type === 'repository')} />
+
+
+
+ {/* Add Integration Modal */}
+
setShowAddModal(false)} title="Add Integration" size="lg">
+
+ {/* Step 1: Choose Type */}
+
+
+
+
+
+
+
+
+ {/* Step 2: Choose Provider */}
+
+
+
+ {providers[addType].map(provider => (
+
+ ))}
+
+
+
+ {/* Step 3: Configure */}
+ {selectedProvider && (
+
+ )}
+
+
+
+ )
+}
+
+function IntegrationList({ integrations }: { integrations: Integration[] }) {
+ if (integrations.length === 0) {
+ return (
+
+ π
+ No integrations yet
+ Add your first integration to get started
+
+ )
+ }
+
+ return (
+
+ {integrations.map(integration => (
+
+ ))}
+
+ )
+}
+
+function IntegrationCard({ integration }: { integration: Integration }) {
+ const [showConfig, setShowConfig] = useState(false)
+ const provider = [...providers.issue_tracker, ...providers.repository].find(p => p.id === integration.provider)
+
+ return (
+ <>
+
+
+
+
+ {provider?.icon || 'π'}
+
+
+
+
{integration.name}
+
+ {integration.status}
+
+
+
{provider?.name} β’ {integration.config.url}
+
+
+
+
+
+
+
+ {integration.lastSync && (
+
+ Last synced: {new Date(integration.lastSync).toLocaleString()}
+
+ )}
+
+
+ setShowConfig(false)} title={`Configure ${integration.name}`} size="lg">
+
+
+ >
+ )
+}
+
+function ProviderConfigForm({ provider, type, initialValues = {} }: {
+ provider: string
+ type: 'issue_tracker' | 'repository'
+ initialValues?: Record
+}) {
+ // Forms especΓficos por provider
+ if (provider === 'jira') {
+ return (
+
+ )
+ }
+
+ if (provider === 'servicenow') {
+ return (
+
+ )
+ }
+
+ if (provider === 'github' || provider === 'gitlab' || provider === 'gitea') {
+ return (
+
+ )
+ }
+
+ // Default form
+ return (
+
+ )
+}
diff --git a/src/pages/Rules.tsx b/src/pages/Rules.tsx
new file mode 100644
index 0000000..c3c64ac
--- /dev/null
+++ b/src/pages/Rules.tsx
@@ -0,0 +1,283 @@
+import { useState } from 'react'
+import { Card, CardHeader, CardContent } from '../components/ui/Card'
+import { Button } from '../components/ui/Button'
+import { Input } from '../components/ui/Input'
+import { Select } from '../components/ui/Select'
+import { Badge } from '../components/ui/Badge'
+import { Modal } from '../components/ui/Modal'
+
+interface Rule {
+ id: string
+ name: string
+ description: string
+ enabled: boolean
+ trigger: {
+ type: string
+ conditions: any[]
+ }
+ actions: any[]
+ stats: {
+ triggered: number
+ successful: number
+ }
+}
+
+const mockRules: Rule[] = [
+ {
+ id: '1',
+ name: 'Auto-analyze Critical Bugs',
+ description: 'Automatically analyze any ticket marked as Critical priority',
+ enabled: true,
+ trigger: { type: 'ticket_created', conditions: [{ field: 'priority', op: 'eq', value: 'critical' }] },
+ actions: [{ type: 'analyze' }, { type: 'create_pr', auto: true }],
+ stats: { triggered: 45, successful: 42 },
+ },
+ {
+ id: '2',
+ name: 'COBOL Module Analysis',
+ description: 'Analyze tickets that mention COBOL programs',
+ enabled: true,
+ trigger: { type: 'ticket_created', conditions: [{ field: 'description', op: 'contains', value: '.CBL' }] },
+ actions: [{ type: 'analyze' }, { type: 'notify', channel: 'slack' }],
+ stats: { triggered: 23, successful: 21 },
+ },
+ {
+ id: '3',
+ name: 'Weekend Batch Jobs',
+ description: 'Queue analysis for weekend processing',
+ enabled: false,
+ trigger: { type: 'schedule', cron: '0 22 * * FRI' },
+ actions: [{ type: 'batch_analyze' }],
+ stats: { triggered: 8, successful: 8 },
+ },
+]
+
+export default function Rules() {
+ const [rules, setRules] = useState(mockRules)
+ const [showAddModal, setShowAddModal] = useState(false)
+
+ const toggleRule = (id: string) => {
+ setRules(rules.map(r => r.id === id ? { ...r, enabled: !r.enabled } : r))
+ }
+
+ return (
+
+
+
+
Automation Rules
+
Configure when and how issues are analyzed
+
+
+
+
+ {/* Stats */}
+
+
+
+
{rules.length}
+
Total Rules
+
+
+
+
+
{rules.filter(r => r.enabled).length}
+
Active
+
+
+
+
+
{rules.reduce((a, r) => a + r.stats.triggered, 0)}
+
Total Triggers
+
+
+
+
+
+ {Math.round(rules.reduce((a, r) => a + r.stats.successful, 0) / rules.reduce((a, r) => a + r.stats.triggered, 0) * 100) || 0}%
+
+
Success Rate
+
+
+
+
+ {/* Rules List */}
+
+ {rules.map(rule => (
+ toggleRule(rule.id)} />
+ ))}
+
+
+ {/* Add Rule Modal */}
+
setShowAddModal(false)} title="Create Automation Rule" size="xl">
+ setShowAddModal(false)} />
+
+
+ )
+}
+
+function RuleCard({ rule, onToggle }: { rule: Rule; onToggle: () => void }) {
+ const [expanded, setExpanded] = useState(false)
+
+ return (
+
+
+
+
+
+
+
+
{rule.name}
+
+ {rule.enabled ? 'Active' : 'Disabled'}
+
+
+
{rule.description}
+
+
+
+
+
Triggered: {rule.stats.triggered}
+
Success: {rule.stats.successful}
+
+
+
+
+
+
+ {expanded && (
+
+
+
+
Trigger
+
+
When: {rule.trigger.type.replace('_', ' ')}
+ {rule.trigger.conditions.map((c, i) => (
+
+ If {c.field} {c.op} "{c.value}"
+
+ ))}
+
+
+
+
Actions
+
+ {rule.actions.map((a, i) => (
+
+ β
+ {a.type.replace('_', ' ')}
+ {a.auto && Auto}
+
+ ))}
+
+
+
+
+
+
+
+
+
+ )}
+
+ )
+}
+
+function RuleBuilder({ onSave }: { onSave: () => void }) {
+ return (
+
+
+
+
+
+
Trigger
+
+
+
+
+
+
+
+
+
+
+
Actions
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx
index 3089da0..3beabaf 100644
--- a/src/pages/Settings.tsx
+++ b/src/pages/Settings.tsx
@@ -1,119 +1,249 @@
+import { Card } from '../components/ui/Card'
+import { Input } from '../components/ui/Input'
+import { Select } from '../components/ui/Select'
+import { Button } from '../components/ui/Button'
+import { Tabs, TabsList, TabsTrigger, TabsContent } from '../components/ui/Tabs'
+
export default function Settings() {
return (
-
Settings
-
- {/* Webhook Endpoints */}
-
-
Webhook Endpoints
-
-
-
-
-
+
+
Settings
+
Configure your JIRA AI Fixer instance
-
- {/* Issue Trackers */}
-
-
Issue Tracker Integrations
-
-
-
-
-
-
-
-
- {/* AI Settings */}
-
-
AI Configuration
-
-
-
-
-
-
-
-
-
-
-
-
-
-
0%
-
70%
-
100%
+
+
+
+ General
+ AI Configuration
+ Notifications
+ Security
+ API
+
+
+
+
+ Organization
+
+
+
+
+
+
+
+
-
-
-
-
- )
-}
+
+
-function EndpointRow({ method, path, description }: {
- method: string
- path: string
- description: string
-}) {
- return (
-
-
- {method}
-
- {path}
- {description}
-
-
- )
-}
+
+
+ AI Model Configuration
+
+
+
+
+
+
+
Analysis Settings
+
+
+
+
+
+ 0%
+ 70% (recommended)
+ 100%
+
+
+
+
+
+
+
+
+
+
+
+
-function TrackerCard({ name, icon, status }: {
- name: string
- icon: string
- status: 'active' | 'ready'
-}) {
- return (
-
-
- {icon}
- {name}
-
-
- {status}
-
+
+
+ Notification Preferences
+
+
+
Email Notifications
+
+ {[
+ 'New issue received',
+ 'Analysis complete',
+ 'PR created',
+ 'PR merged',
+ 'Analysis failed',
+ 'Daily summary',
+ 'Weekly report',
+ ].map(item => (
+
+ ))}
+
+
+
+
+
Slack Integration
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Security Settings
+
+
+
Authentication
+
+
+
+
+
Session
+
+
+
+
+
+
IP Allowlist
+
+
Leave empty to allow all IPs
+
+
+
+
+
+
+
+
+
+
+
+ API Access
+
+
+
API Keys
+
+
+
+
pk_live_β’β’β’β’β’β’β’β’β’β’β’β’β’β’β’β’
+
Created Feb 18, 2026
+
+
+
+
+
+
+
+
+
+
+
+
Webhook Endpoints
+
+
POST /api/webhook/tickethub
+
POST /api/webhook/jira
+
POST /api/webhook/servicenow
+
POST /api/webhook/github
+
+
+
+
+
Rate Limits
+
+
+
Requests/minute
+
1,000
+
+
+
+
+
+
+
+
)
}
diff --git a/src/pages/Team.tsx b/src/pages/Team.tsx
new file mode 100644
index 0000000..38c2c94
--- /dev/null
+++ b/src/pages/Team.tsx
@@ -0,0 +1,166 @@
+import { useState } from 'react'
+import { Card } from '../components/ui/Card'
+import { Button } from '../components/ui/Button'
+import { Input } from '../components/ui/Input'
+import { Select } from '../components/ui/Select'
+import { Badge } from '../components/ui/Badge'
+import { Modal } from '../components/ui/Modal'
+
+interface TeamMember {
+ id: string
+ name: string
+ email: string
+ role: 'admin' | 'developer' | 'viewer'
+ avatar: string
+ lastActive: string
+ stats: {
+ issuesResolved: number
+ prsReviewed: number
+ }
+}
+
+const mockTeam: TeamMember[] = [
+ {
+ id: '1',
+ name: 'Ricel Leite',
+ email: 'ricel.souza@gmail.com',
+ role: 'admin',
+ avatar: 'π¨βπ»',
+ lastActive: '2026-02-18T18:00:00Z',
+ stats: { issuesResolved: 45, prsReviewed: 32 },
+ },
+ {
+ id: '2',
+ name: 'AI Assistant',
+ email: 'ai@jira-fixer.local',
+ role: 'developer',
+ avatar: 'π€',
+ lastActive: '2026-02-18T18:30:00Z',
+ stats: { issuesResolved: 127, prsReviewed: 89 },
+ },
+]
+
+export default function Team() {
+ const [team] = useState
(mockTeam)
+ const [showInvite, setShowInvite] = useState(false)
+
+ return (
+
+
+
+
Team
+
Manage team members and permissions
+
+
+
+
+ {/* Role Legend */}
+
+
+ Admin
+ Full access
+
+
+ Developer
+ Can analyze & create PRs
+
+
+ Viewer
+ Read-only access
+
+
+
+ {/* Team Grid */}
+
+ {team.map(member => (
+
+ ))}
+
+
+ {/* Invite Modal */}
+
setShowInvite(false)} title="Invite Team Member">
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+function TeamCard({ member }: { member: TeamMember }) {
+ const roleColors = {
+ admin: 'error' as const,
+ developer: 'info' as const,
+ viewer: 'default' as const,
+ }
+
+ return (
+
+
+
+ {member.avatar}
+
+
+
+
{member.name}
+ {member.role}
+
+
{member.email}
+
+ Active {new Date(member.lastActive).toLocaleDateString()}
+
+
+
+
+
+
+
{member.stats.issuesResolved}
+
Issues Resolved
+
+
+
{member.stats.prsReviewed}
+
PRs Reviewed
+
+
+
+
+
+
+
+
+ )
+}