add feedback button on all CSC pages

This commit is contained in:
Tykayn 2025-08-08 16:43:50 +02:00 committed by tykayn
parent 13f3220456
commit 14cdf17ec3
16 changed files with 590 additions and 5 deletions

View file

@ -0,0 +1,191 @@
.feedback-button {
background: #ecf3fa;
color: #083b7d;
padding: 8px;
border-radius: 8px;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
transform: rotate(270deg);
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
margin-right: -35px;
position: fixed;
right: 0;
top: 240px;
z-index: 100;
&:hover {
background: #d9e8f6;
}
i {
font-size: 16px;
}
}
// Modal styles
.feedback-modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.feedback-modal {
background: white;
border-radius: 8px;
width: 90%;
max-width: 500px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
display: flex;
flex-direction: column;
max-height: 90vh;
overflow: hidden;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid #eee;
h3 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #083b7d;
}
.close-button {
background: none;
border: none;
cursor: pointer;
font-size: 20px;
color: #666;
padding: 4px;
&:hover {
color: #333;
}
}
}
.modal-body {
padding: 20px;
overflow-y: auto;
.modal-description {
margin-bottom: 16px;
color: #555;
}
textarea {
width: 100%;
padding: 12px;
border: 1px solid #ddd;
border-radius: 4px;
resize: vertical;
font-family: inherit;
font-size: 14px;
&:focus {
outline: none;
border-color: #083b7d;
}
&:disabled {
background-color: #f5f5f5;
cursor: not-allowed;
}
}
.success-message, .error-message {
margin-top: 16px;
padding: 10px;
border-radius: 4px;
display: flex;
align-items: center;
gap: 8px;
i {
font-size: 18px;
}
}
.success-message {
background-color: #e6f7e6;
color: #2e7d32;
}
.error-message {
background-color: #fdecea;
color: #d32f2f;
}
}
.modal-footer {
padding: 16px 20px;
border-top: 1px solid #eee;
display: flex;
justify-content: flex-end;
gap: 12px;
button {
padding: 8px 16px;
border-radius: 4px;
font-weight: 500;
cursor: pointer;
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
}
.cancel-button {
background: none;
border: 1px solid #ddd;
color: #555;
&:hover:not(:disabled) {
background-color: #f5f5f5;
}
}
.submit-button {
background-color: #083b7d;
color: white;
border: none;
display: flex;
align-items: center;
gap: 8px;
&:hover:not(:disabled) {
background-color: #062c5e;
}
}
}
// Spinner animation
.spinning {
animation: spin 1s linear infinite;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

View file

@ -0,0 +1,67 @@
<button (click)="toggleModal()" class="feedback-button">
<span class="text">
Feedback
</span>
<i class="ri-message-2-line"></i>
</button>
<!-- Feedback Modal -->
@if (isModalOpen) {
<div class="feedback-modal-overlay">
<div class="feedback-modal">
<div class="modal-header">
<h3>Send Feedback</h3>
<button class="close-button" (click)="toggleModal()">
<i class="ri-close-line"></i>
</button>
</div>
<div class="modal-body">
<p class="modal-description">
Help us improve by sharing your feedback, suggestions, or reporting issues.
</p>
<textarea
[(ngModel)]="feedbackText"
placeholder="Enter your feedback here..."
[disabled]="isSubmitting"
rows="5"
></textarea>
@if (submitSuccess) {
<div class="success-message">
<i class="ri-check-line"></i> Thank you for your feedback!
</div>
}
@if (submitError) {
<div class="error-message">
<i class="ri-error-warning-line"></i> Failed to submit feedback. Please try again.
</div>
}
</div>
<div class="modal-footer">
<button
class="cancel-button"
(click)="toggleModal()"
[disabled]="isSubmitting"
>
Cancel
</button>
<button
class="submit-button"
(click)="submitFeedback()"
[disabled]="isSubmitting || !feedbackText.trim()"
>
@if (isSubmitting) {
<i class="ri-loader-4-line spinning"></i> Sending...
} @else {
Send Feedback
}
</button>
</div>
</div>
</div>
}

View file

@ -0,0 +1,192 @@
.feedback-button {
background: #ecf3fa;
color: #083b7d;
padding: 8px;
border-radius: 8px;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
transform: rotate(270deg);
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
margin-right: -35px;
position: fixed;
right: 0;
top: 240px;
z-index: 100;
&:hover {
background: #d9e8f6;
}
i {
font-size: 16px;
}
}
// Modal styles
.feedback-modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.feedback-modal {
background: white;
border-radius: 8px;
width: 90%;
max-width: 500px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
display: flex;
flex-direction: column;
max-height: 90vh;
overflow: hidden;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid #eee;
h3 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #083b7d;
}
.close-button {
background: none;
border: none;
cursor: pointer;
font-size: 20px;
color: #666;
padding: 4px;
&:hover {
color: #333;
}
}
}
.modal-body {
padding: 20px;
overflow-y: auto;
.modal-description {
margin-bottom: 16px;
color: #555;
}
textarea {
width: 100%;
padding: 12px;
border: 1px solid #ddd;
border-radius: 4px;
resize: vertical;
font-family: inherit;
font-size: 14px;
&:focus {
outline: none;
border-color: #083b7d;
}
&:disabled {
background-color: #f5f5f5;
cursor: not-allowed;
}
}
.success-message, .error-message {
margin-top: 16px;
padding: 10px;
border-radius: 4px;
display: flex;
align-items: center;
gap: 8px;
i {
font-size: 18px;
}
}
.success-message {
background-color: #e6f7e6;
color: #2e7d32;
}
.error-message {
background-color: #fdecea;
color: #d32f2f;
}
}
.modal-footer {
padding: 16px 20px;
border-top: 1px solid #eee;
display: flex;
justify-content: flex-end;
gap: 12px;
button {
padding: 8px 16px;
border-radius: 4px;
font-weight: 500;
cursor: pointer;
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
}
.cancel-button {
background: none;
border: 1px solid #ddd;
color: #555;
&:hover:not(:disabled) {
background-color: #f5f5f5;
}
}
.submit-button {
background-color: #083b7d;
color: white;
border: none;
display: flex;
align-items: center;
gap: 8px;
&:hover:not(:disabled) {
background-color: #062c5e;
}
}
}
// Spinner animation
.spinning {
animation: spin 1s linear infinite;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

View file

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FeedbackButton } from './feedback-button';
describe('FeedbackButton', () => {
let component: FeedbackButton;
let fixture: ComponentFixture<FeedbackButton>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [FeedbackButton]
})
.compileComponents();
fixture = TestBed.createComponent(FeedbackButton);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -0,0 +1,88 @@
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
@Component({
selector: 'sae-feedback-button',
imports: [
FormsModule
],
templateUrl: './feedback-button.html',
styleUrl: './feedback-button.css'
})
export class FeedbackButton {
isModalOpen: boolean = false;
feedbackText: string = '';
isSubmitting: boolean = false;
submitSuccess: boolean = false;
submitError: boolean = false;
// constructor(
// private store: Store<StateInterface>,
// private apiService: ApiService
// ) {
// }
toggleModal() {
this.isModalOpen = !this.isModalOpen;
// Reset state when opening modal
if (this.isModalOpen) {
this.feedbackText = '';
this.submitSuccess = false;
this.submitError = false;
}
// Update app state to show/hide feedback panel
// this.store.dispatch({
// type: ActionTypes.UPDATE_APP,
// payload: {
// displayFeedBackPanel: this.isModalOpen,
// feedBackInput: this.feedbackText
// }
// });
}
async submitFeedback() {
if (!this.feedbackText.trim()) {
return; // Don't submit empty feedback
}
this.isSubmitting = true;
this.submitSuccess = false;
this.submitError = false;
try {
// Update Redux state with feedback text
// this.store.dispatch({
// type: ActionTypes.UPDATE_APP,
// payload: {
// feedBackInput: this.feedbackText
// }
// });
//
// // Dispatch action to send feedback
// this.store.dispatch({
// type: ActionTypes.SEND_USER_FEEDBACK,
// payload: {
// feedback: this.feedbackText
// }
// });
//
// // Call API service directly
// await this.apiService.sendUserFeedback(this.feedbackText);
this.submitSuccess = true;
// Close modal after a short delay
setTimeout(() => {
this.toggleModal();
}, 2000);
} catch (error) {
console.error('Error submitting feedback:', error);
this.submitError = true;
} finally {
this.isSubmitting = false;
}
}
}

View file

@ -3,11 +3,13 @@
<title>CSC implémentation</title> <title>CSC implémentation</title>
</head> </head>
<body> <body>
<app-top-navigation></app-top-navigation>
<router-outlet/> <router-outlet/>
<h1> <h1>
<img src="csc_logo.svg" alt="logo"> <img src="csc_logo.svg" alt="logo">
CSC CSC
</h1> </h1>
<sae-feedback-button></sae-feedback-button>
</body> </body>
</html> </html>

View file

@ -1,10 +1,12 @@
import { Component, signal } from '@angular/core'; import { Component, signal } from '@angular/core';
import { RouterOutlet } from '@angular/router'; import { RouterOutlet } from '@angular/router';
import {TopNavigation} from './shared/navigation/top-navigation/top-navigation';
import {FeedbackButton} from 'sae-lib/buttons/feedback-button/feedback-button';
// import {SaeLib} from '@sae-lib/src/public-api'; // import {SaeLib} from '@sae-lib/src/public-api';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
imports: [RouterOutlet], imports: [RouterOutlet, TopNavigation, FeedbackButton],
templateUrl: './app.html', templateUrl: './app.html',
styleUrl: './app.scss' styleUrl: './app.scss'
}) })

View file

@ -1,5 +1,4 @@
<div id="main_page"> <div id="main_page">
<app-top-navigation></app-top-navigation>
<main> <main>
<h2 class="title"> <h2 class="title">

View file

@ -8,7 +8,6 @@ import {BottomNavigation} from '../../shared/navigation/bottom-navigation/bottom
@Component({ @Component({
selector: 'app-main', selector: 'app-main',
imports: [ imports: [
TopNavigation,
TranslateTexts, TranslateTexts,
FiltersGroup, FiltersGroup,
BottomNavigation BottomNavigation

View file

@ -0,0 +1,9 @@
export type CscCase = {
};
export type TechManual = {
};
export type filterListing = {
};

View file

@ -5,7 +5,7 @@ import {StateInterface} from './reducers';
export const initialState: StateInterface = { export const initialState: StateInterface = {
app: { app: {
backendAPIRoot: "", backendAPIRoot: "",
demoMode: true, // selon l'environnement demoMode: true,
}, },
currentLang: "fr_FR", currentLang: "fr_FR",
currentTheme: "light", currentTheme: "light",

View file

@ -1,2 +1,6 @@
// Ce fichier regroupe toutes les variables de sae-lib pour faciliter l'importation // Ce fichier regroupe toutes les variables de sae-lib pour faciliter l'importation
@forward 'sae-lib/src/styles/variables.scss'; @forward 'sae-lib/src/styles/variables.scss';
.all-pages{
padding: 50px 64px;
}

View file

@ -2,7 +2,7 @@
#main_page { #main_page {
main { main {
padding: 50px 64px; @extend .all-pages;
.title { .title {
font-size: 20px; font-size: 20px;
@ -143,6 +143,8 @@
justify-content: flex-end; justify-content: flex-end;
align-items: center; align-items: center;
background: white; background: white;
left: 0;
z-index: 100;
box-shadow: 0 -10px 20px 0 rgba(30, 31, 34, 0.05); box-shadow: 0 -10px 20px 0 rgba(30, 31, 34, 0.05);

View file

@ -0,0 +1,5 @@
@use '../../../../styles/variables-barrel' as variables;
.similar-cases{
@extend .all-pages;
}

View file

@ -1,2 +1,4 @@
@use "app/styles/pages/main.scss"; @use "app/styles/pages/main.scss";
@use "app/styles/pages/similar-cases.scss";
@use "app/styles/pages/admin.scss";