feat: jira and synchro

This commit is contained in:
Julien Froidefond
2025-09-17 13:56:42 +02:00
parent 2f104109db
commit 625e8dba4b
24 changed files with 1821 additions and 140 deletions

View File

@@ -0,0 +1,38 @@
import { NextRequest, NextResponse } from 'next/server';
import { prisma } from '@/services/database';
/**
* Route GET /api/jira/logs
* Récupère les logs de synchronisation Jira
*/
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url);
const limit = parseInt(searchParams.get('limit') || '10');
const logs = await prisma.syncLog.findMany({
where: {
source: 'jira'
},
orderBy: {
createdAt: 'desc'
},
take: limit
});
return NextResponse.json({
data: logs
});
} catch (error) {
console.error('❌ Erreur récupération logs Jira:', error);
return NextResponse.json(
{
error: 'Erreur lors de la récupération des logs',
details: error instanceof Error ? error.message : 'Erreur inconnue'
},
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,96 @@
import { NextRequest, NextResponse } from 'next/server';
import { createJiraService } from '@/services/jira';
/**
* Route POST /api/jira/sync
* Synchronise les tickets Jira avec la base locale
*/
export async function POST(request: NextRequest) {
try {
const jiraService = createJiraService();
if (!jiraService) {
return NextResponse.json(
{ error: 'Configuration Jira manquante. Vérifiez les variables d\'environnement JIRA_BASE_URL, JIRA_EMAIL et JIRA_API_TOKEN.' },
{ status: 400 }
);
}
console.log('🔄 Début de la synchronisation Jira...');
// Tester la connexion d'abord
const connectionOk = await jiraService.testConnection();
if (!connectionOk) {
return NextResponse.json(
{ error: 'Impossible de se connecter à Jira. Vérifiez la configuration.' },
{ status: 401 }
);
}
// Effectuer la synchronisation
const result = await jiraService.syncTasks();
if (result.success) {
return NextResponse.json({
message: 'Synchronisation Jira terminée avec succès',
data: result
});
} else {
return NextResponse.json(
{
error: 'Synchronisation Jira terminée avec des erreurs',
data: result
},
{ status: 207 } // Multi-Status
);
}
} catch (error) {
console.error('❌ Erreur API sync Jira:', error);
return NextResponse.json(
{
error: 'Erreur interne lors de la synchronisation',
details: error instanceof Error ? error.message : 'Erreur inconnue'
},
{ status: 500 }
);
}
}
/**
* Route GET /api/jira/sync
* Teste la connexion Jira
*/
export async function GET() {
try {
const jiraService = createJiraService();
if (!jiraService) {
return NextResponse.json(
{
connected: false,
message: 'Configuration Jira manquante'
}
);
}
const connected = await jiraService.testConnection();
return NextResponse.json({
connected,
message: connected ? 'Connexion Jira OK' : 'Impossible de se connecter à Jira'
});
} catch (error) {
console.error('❌ Erreur test connexion Jira:', error);
return NextResponse.json(
{
connected: false,
message: 'Erreur lors du test de connexion',
details: error instanceof Error ? error.message : 'Erreur inconnue'
}
);
}
}

View File

@@ -9,7 +9,7 @@ import { Card } from '@/components/ui/Card';
import { DailyCalendar } from '@/components/daily/DailyCalendar';
import { DailySection } from '@/components/daily/DailySection';
import { dailyClient } from '@/clients/daily-client';
import { SimpleHeader } from '@/components/ui/SimpleHeader';
import { Header } from '@/components/ui/Header';
interface DailyPageClientProps {
initialDailyView?: DailyView;
@@ -151,7 +151,7 @@ export function DailyPageClient({
return (
<div className="min-h-screen bg-[var(--background)]">
{/* Header uniforme */}
<SimpleHeader
<Header
title="TowerControl"
subtitle="Daily - Gestion quotidienne"
syncing={saving}

15
src/app/settings/page.tsx Normal file
View File

@@ -0,0 +1,15 @@
import { SettingsPageClient } from '@/components/settings/SettingsPageClient';
import { getConfig } from '@/lib/config';
// Force dynamic rendering (no static generation)
export const dynamic = 'force-dynamic';
export default async function SettingsPage() {
const config = getConfig();
return (
<SettingsPageClient
config={config}
/>
);
}

View File

@@ -8,7 +8,7 @@ import { CreateTagData } from '@/clients/tags-client';
import { Button } from '@/components/ui/Button';
import { Input } from '@/components/ui/Input';
import { TagForm } from '@/components/forms/TagForm';
import { SimpleHeader } from '@/components/ui/SimpleHeader';
import { Header } from '@/components/ui/Header';
interface TagsPageClientProps {
initialTags: Tag[];
@@ -83,7 +83,7 @@ export function TagsPageClient({ initialTags }: TagsPageClientProps) {
return (
<div className="min-h-screen bg-[var(--background)]">
{/* Header uniforme */}
<SimpleHeader
<Header
title="TowerControl"
subtitle="Tags - Gestion des étiquettes"
syncing={loading}

View File

@@ -60,7 +60,12 @@ export function TasksProvider({ children, initialTasks, initialStats, initialTag
sortBy: preferences.kanbanFilters.sortBy || createSortKey('priority', 'desc'),
compactView: preferences.viewPreferences.compactView || false,
swimlanesByTags: preferences.viewPreferences.swimlanesByTags || false,
swimlanesMode: preferences.viewPreferences.swimlanesMode || 'tags'
swimlanesMode: preferences.viewPreferences.swimlanesMode || 'tags',
// Filtres Jira
showJiraOnly: preferences.kanbanFilters.showJiraOnly || false,
hideJiraTasks: preferences.kanbanFilters.hideJiraTasks || false,
jiraProjects: preferences.kanbanFilters.jiraProjects || [],
jiraTypes: preferences.kanbanFilters.jiraTypes || []
}), [preferences]);
// Fonction pour mettre à jour les filtres avec persistance
@@ -71,7 +76,12 @@ export function TasksProvider({ children, initialTasks, initialStats, initialTag
tags: newFilters.tags,
priorities: newFilters.priorities,
showCompleted: newFilters.showCompleted,
sortBy: newFilters.sortBy
sortBy: newFilters.sortBy,
// Filtres Jira
showJiraOnly: newFilters.showJiraOnly,
hideJiraTasks: newFilters.hideJiraTasks,
jiraProjects: newFilters.jiraProjects,
jiraTypes: newFilters.jiraTypes
};
const viewPreferenceUpdates = {
@@ -146,6 +156,27 @@ export function TasksProvider({ children, initialTasks, initialStats, initialTag
);
}
// Filtres spécifiques Jira
if (kanbanFilters.showJiraOnly) {
filtered = filtered.filter(task => task.source === 'jira');
} else if (kanbanFilters.hideJiraTasks) {
filtered = filtered.filter(task => task.source !== 'jira');
}
// Filtre par projets Jira
if (kanbanFilters.jiraProjects?.length) {
filtered = filtered.filter(task =>
task.source !== 'jira' || kanbanFilters.jiraProjects!.includes(task.jiraProject || '')
);
}
// Filtre par types Jira
if (kanbanFilters.jiraTypes?.length) {
filtered = filtered.filter(task =>
task.source !== 'jira' || kanbanFilters.jiraTypes!.includes(task.jiraType || '')
);
}
// Tri des tâches
if (kanbanFilters.sortBy) {
const sortOption = getSortOption(kanbanFilters.sortBy);