Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion lib/data/state/analysis_charts_visibility_notifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ class AnalysisChartsVisibilityNotifier
: super({
'globalStats': true,
'questionsAnalysis': true,
'globalDistribution': true,
'questionsDetailed': true,
'atRiskEmployees': true,
});
Expand Down
33 changes: 23 additions & 10 deletions lib/data/utils/qvst_chart_utils.dart
Original file line number Diff line number Diff line change
@@ -1,24 +1,37 @@
import 'package:flutter/material.dart';

class QvstChartUtils {
static const List<Color> globalScoreColors = [
Color(0xFF388E3C),
Color(0xFF8BC34A),
Color(0xFF9E9E9E),
Color(0xFFF57C00),
Color(0xFFD32F2F),
];

static String getLabelForScore(int score, {Map<int, String>? labels}) {
if (labels != null && labels.containsKey(score)) {
return labels[score]!;
}
return 'Note $score';
}

static Color getColorForScore(int score) {
/// Returns the color for a given score, with optional inversion for reversed questions.
/// When `isReversed` is false (positive question): mapping is green → red (score 5 = green, score 1 = red).
/// When `isReversed` is true (negative question): mapping is inverted so that favorable responses stay green.
static Color getColorForScore(int score, {bool isReversed = false}) {
if (score < 1 || score > 5) return Colors.grey;
return globalScoreColors[score - 1];

const Map<int, Color> positiveMapping = {
5: Color(0xFF388E3C), // Green: Strongly agree / very positive
4: Color(0xFF8BC34A), // Light green
3: Color(0xFF9E9E9E), // Neutral (grey/yellow)
2: Color(0xFFF57C00), // Orange
1: Color(0xFFD32F2F), // Red: Strongly disagree / very negative
};

const Map<int, Color> reversedMapping = {
5: Color(0xFFD32F2F), // Red: high score is negative in this context
4: Color(0xFFF57C00), // Orange
3: Color(0xFF9E9E9E), // Neutral
2: Color(0xFF8BC34A), // Light green
1: Color(0xFF388E3C), // Green: low score is positive in this context
};

final mapping = isReversed ? reversedMapping : positiveMapping;
return mapping[score] ?? Colors.grey;
}

static Color getSatisfactionColor(double satisfaction) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:syncfusion_flutter_charts/charts.dart';
import 'package:xpeapp_admin/data/entities/qvst/analysis/qvst_analysis_entity.dart';
import 'package:xpeapp_admin/data/utils/qvst_chart_utils.dart';
import 'package:xpeapp_admin/data/utils/qvst_ui_utils.dart';
import 'package:xpeapp_admin/presentation/widgets/common/collapsible_card.dart';
import 'package:xpeapp_admin/presentation/widgets/common/screenshot_button.dart';
import 'package:xpeapp_admin/providers.dart';

class QuestionsSelectionPieChart extends StatefulWidget {
class QuestionsSelectionPieChart extends ConsumerStatefulWidget {
final List<QuestionAnalysisEntity> questionsAnalysis;

const QuestionsSelectionPieChart({
Expand All @@ -15,12 +17,12 @@ class QuestionsSelectionPieChart extends StatefulWidget {
});

@override
State<QuestionsSelectionPieChart> createState() =>
ConsumerState<QuestionsSelectionPieChart> createState() =>
_QuestionsSelectionPieChartState();
}

class _QuestionsSelectionPieChartState
extends State<QuestionsSelectionPieChart> {
extends ConsumerState<QuestionsSelectionPieChart> {
static const int _maxScore = 5;
final Set<String> _selectedQuestionIds = <String>{};

Expand Down Expand Up @@ -49,6 +51,7 @@ class _QuestionsSelectionPieChartState

@override
Widget build(BuildContext context) {
final reversedQuestions = ref.watch(reversedQuestionsProvider);
final chartKey = GlobalKey();

if (widget.questionsAnalysis.isEmpty) {
Expand Down Expand Up @@ -100,7 +103,7 @@ class _QuestionsSelectionPieChartState
else
RepaintBoundary(
key: chartKey,
child: _buildPieChart(selectedQuestions),
child: _buildPieChart(selectedQuestions, reversedQuestions),
),
],
),
Expand Down Expand Up @@ -145,7 +148,8 @@ class _QuestionsSelectionPieChartState
);
}

Widget _buildPieChart(List<QuestionAnalysisEntity> selectedQuestions) {
Widget _buildPieChart(List<QuestionAnalysisEntity> selectedQuestions,
Map<String, bool> reversedQuestions) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expand All @@ -166,7 +170,8 @@ class _QuestionsSelectionPieChartState
childAspectRatio: 1,
physics: const NeverScrollableScrollPhysics(),
children: selectedQuestions.map((question) {
final data = _buildChartDataForQuestion(question);
final isReversed = reversedQuestions[question.questionId] ?? false;
final data = _buildChartDataForQuestion(question, isReversed);
final totalResponses = data.fold<int>(0, (s, d) => s + d.count);

return Card(
Expand Down Expand Up @@ -275,7 +280,7 @@ class _QuestionsSelectionPieChartState
}

List<_PieChartData> _buildChartDataForQuestion(
QuestionAnalysisEntity question) {
QuestionAnalysisEntity question, bool isReversed) {
final countsByScore = <int, int>{for (var i = 1; i <= _maxScore; i++) i: 0};
final answerLabels = <int, String>{};

Expand All @@ -294,14 +299,17 @@ class _QuestionsSelectionPieChartState
}
}

return List.generate(_maxScore, (i) {
final score = i + 1;
// Generate data sorted by descending score (5→1) so segments are displayed clockwise
final result = List.generate(_maxScore, (i) {
final score = _maxScore - i; // Trier décroissant: 5, 4, 3, 2, 1
return _PieChartData(
label: QvstChartUtils.getLabelForScore(score, labels: answerLabels),
count: countsByScore[score] ?? 0,
color: QvstChartUtils.getColorForScore(score),
color: QvstChartUtils.getColorForScore(score, isReversed: isReversed),
);
}).where((data) => data.count > 0).toList();

return result;
}

void _toggleQuestion(String questionId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ void main() {
expect(notifier.state, {
'globalStats': true,
'questionsAnalysis': true,
'globalDistribution': true,
'questionsDetailed': true,
'atRiskEmployees': true,
});
Expand Down
34 changes: 34 additions & 0 deletions test/data/utils/qvst_chart_utils_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:xpeapp_admin/data/utils/qvst_chart_utils.dart';

void main() {
group('QvstChartUtils.getColorForScore', () {
test('returns green for score 5 when not reversed', () {
final color = QvstChartUtils.getColorForScore(5, isReversed: false);
expect(color, equals(const Color(0xFF388E3C)));
});

test('returns red for score 1 when not reversed', () {
final color = QvstChartUtils.getColorForScore(1, isReversed: false);
expect(color, equals(const Color(0xFFD32F2F)));
});

test('returns inverted mapping when reversed=true (score 1 -> green)', () {
final color = QvstChartUtils.getColorForScore(1, isReversed: true);
expect(color, equals(const Color(0xFF388E3C)));
});

test('returns inverted mapping when reversed=true (score 5 -> red)', () {
final color = QvstChartUtils.getColorForScore(5, isReversed: true);
expect(color, equals(const Color(0xFFD32F2F)));
});

test('out-of-range returns grey', () {
final colorLow = QvstChartUtils.getColorForScore(0);
final colorHigh = QvstChartUtils.getColorForScore(6);
expect(colorLow, equals(Colors.grey));
expect(colorHigh, equals(Colors.grey));
});
});
}
Loading