feat: enhance Jira filters and dashboard functionality
- Added new test scripts in `package.json` for story points and Jira fields validation. - Updated `JiraDashboardPageClient` to utilize raw analytics for filtering, improving data handling with active filters. - Introduced a loading state in `FilterBar` with visual feedback for filter application, enhancing user experience. - Refactored `useJiraFilters` to support local filtering based on initial analytics, streamlining filter management. - Enhanced `JiraAnalyticsService` to calculate story points based on issue types, improving accuracy in analytics.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { JiraAnalyticsFilters, AvailableFilters } from '@/lib/types';
|
||||
import { JiraAdvancedFiltersService } from '@/services/integrations/jira/advanced-filters';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
@@ -11,6 +11,7 @@ interface FilterBarProps {
|
||||
availableFilters: AvailableFilters;
|
||||
activeFilters: Partial<JiraAnalyticsFilters>;
|
||||
onFiltersChange: (filters: Partial<JiraAnalyticsFilters>) => void;
|
||||
isLoading?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
@@ -18,18 +19,35 @@ export default function FilterBar({
|
||||
availableFilters,
|
||||
activeFilters,
|
||||
onFiltersChange,
|
||||
isLoading = false,
|
||||
className = ''
|
||||
}: FilterBarProps) {
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [pendingFilters, setPendingFilters] = useState<Partial<JiraAnalyticsFilters>>(activeFilters);
|
||||
|
||||
const hasActiveFilters = JiraAdvancedFiltersService.hasActiveFilters(activeFilters);
|
||||
const activeFiltersCount = JiraAdvancedFiltersService.countActiveFilters(activeFilters);
|
||||
|
||||
const clearAllFilters = () => {
|
||||
const emptyFilters = JiraAdvancedFiltersService.createEmptyFilters();
|
||||
onFiltersChange(emptyFilters);
|
||||
setPendingFilters(emptyFilters);
|
||||
};
|
||||
|
||||
const applyPendingFilters = () => {
|
||||
onFiltersChange(pendingFilters);
|
||||
setShowModal(false);
|
||||
};
|
||||
|
||||
const cancelFilters = () => {
|
||||
setPendingFilters(activeFilters);
|
||||
setShowModal(false);
|
||||
};
|
||||
|
||||
// Synchroniser pendingFilters avec activeFilters quand ils changent
|
||||
useEffect(() => {
|
||||
setPendingFilters(activeFilters);
|
||||
}, [activeFilters]);
|
||||
|
||||
const removeFilter = (filterType: keyof JiraAnalyticsFilters, value: string) => {
|
||||
const currentValues = activeFilters[filterType];
|
||||
if (!currentValues || !Array.isArray(currentValues)) return;
|
||||
@@ -47,7 +65,12 @@ export default function FilterBar({
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-[var(--foreground)]">🔍 Filtres</span>
|
||||
{hasActiveFilters && (
|
||||
{isLoading && (
|
||||
<Badge className="bg-yellow-100 text-yellow-800 text-xs">
|
||||
⏳ Chargement...
|
||||
</Badge>
|
||||
)}
|
||||
{hasActiveFilters && !isLoading && (
|
||||
<Badge className="bg-blue-100 text-blue-800 text-xs">
|
||||
{activeFiltersCount}
|
||||
</Badge>
|
||||
@@ -160,7 +183,16 @@ export default function FilterBar({
|
||||
<Modal
|
||||
isOpen={showModal}
|
||||
onClose={() => setShowModal(false)}
|
||||
title="Configuration des filtres"
|
||||
title={
|
||||
<div className="flex items-center gap-2">
|
||||
Configuration des filtres
|
||||
{isLoading && (
|
||||
<Badge className="bg-yellow-100 text-yellow-800 text-xs">
|
||||
⏳ Chargement...
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
size="lg"
|
||||
>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 max-h-96 overflow-y-auto">
|
||||
@@ -175,14 +207,14 @@ export default function FilterBar({
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={activeFilters.issueTypes?.includes(option.value) || false}
|
||||
checked={pendingFilters.issueTypes?.includes(option.value) || false}
|
||||
onChange={(e) => {
|
||||
const current = activeFilters.issueTypes || [];
|
||||
const current = pendingFilters.issueTypes || [];
|
||||
const newValues = e.target.checked
|
||||
? [...current, option.value]
|
||||
: current.filter(v => v !== option.value);
|
||||
onFiltersChange({
|
||||
...activeFilters,
|
||||
setPendingFilters({
|
||||
...pendingFilters,
|
||||
issueTypes: newValues
|
||||
});
|
||||
}}
|
||||
@@ -206,14 +238,14 @@ export default function FilterBar({
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={activeFilters.statuses?.includes(option.value) || false}
|
||||
checked={pendingFilters.statuses?.includes(option.value) || false}
|
||||
onChange={(e) => {
|
||||
const current = activeFilters.statuses || [];
|
||||
const current = pendingFilters.statuses || [];
|
||||
const newValues = e.target.checked
|
||||
? [...current, option.value]
|
||||
: current.filter(v => v !== option.value);
|
||||
onFiltersChange({
|
||||
...activeFilters,
|
||||
setPendingFilters({
|
||||
...pendingFilters,
|
||||
statuses: newValues
|
||||
});
|
||||
}}
|
||||
@@ -237,14 +269,14 @@ export default function FilterBar({
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={activeFilters.assignees?.includes(option.value) || false}
|
||||
checked={pendingFilters.assignees?.includes(option.value) || false}
|
||||
onChange={(e) => {
|
||||
const current = activeFilters.assignees || [];
|
||||
const current = pendingFilters.assignees || [];
|
||||
const newValues = e.target.checked
|
||||
? [...current, option.value]
|
||||
: current.filter(v => v !== option.value);
|
||||
onFiltersChange({
|
||||
...activeFilters,
|
||||
setPendingFilters({
|
||||
...pendingFilters,
|
||||
assignees: newValues
|
||||
});
|
||||
}}
|
||||
@@ -268,14 +300,14 @@ export default function FilterBar({
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={activeFilters.components?.includes(option.value) || false}
|
||||
checked={pendingFilters.components?.includes(option.value) || false}
|
||||
onChange={(e) => {
|
||||
const current = activeFilters.components || [];
|
||||
const current = pendingFilters.components || [];
|
||||
const newValues = e.target.checked
|
||||
? [...current, option.value]
|
||||
: current.filter(v => v !== option.value);
|
||||
onFiltersChange({
|
||||
...activeFilters,
|
||||
setPendingFilters({
|
||||
...pendingFilters,
|
||||
components: newValues
|
||||
});
|
||||
}}
|
||||
@@ -291,10 +323,11 @@ export default function FilterBar({
|
||||
|
||||
<div className="flex gap-2 pt-6 border-t">
|
||||
<Button
|
||||
onClick={() => setShowModal(false)}
|
||||
onClick={cancelFilters}
|
||||
variant="secondary"
|
||||
className="flex-1"
|
||||
>
|
||||
✅ Fermer
|
||||
❌ Annuler
|
||||
</Button>
|
||||
<Button
|
||||
onClick={clearAllFilters}
|
||||
@@ -303,6 +336,13 @@ export default function FilterBar({
|
||||
>
|
||||
🗑️ Effacer tout
|
||||
</Button>
|
||||
<Button
|
||||
onClick={applyPendingFilters}
|
||||
className="flex-1"
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? '⏳ Application...' : '✅ Appliquer'}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user