Profile
-
Name: {user.name}
-
Email: {user.email}
-
Role: {user.roles[0]?.name || 'N/A'}
-
+
+ Name: {user.name}
+
+
+ Email: {user.email}
+
+
+ Role: {user.roles[0]?.name || 'N/A'}
+
diff --git a/src/pages/Questionnaire.tsx b/src/pages/Questionnaire.tsx
new file mode 100644
index 0000000..f3a7c83
--- /dev/null
+++ b/src/pages/Questionnaire.tsx
@@ -0,0 +1,59 @@
+import React from 'react';
+import { useLocation } from 'react-router-dom';
+import Navbar from '../components/Navbar';
+import DynamicView from '../components/DynamicView';
+import '../styles/Questionnaire.css';
+import { Questionnaire as QuestionnaireType } from '../types/questionnaire';
+
+const Questionnaire: React.FC = () => {
+ const location = useLocation();
+ const questionnaire = location.state?.questionnaire as QuestionnaireType | null;
+
+ if (!questionnaire) {
+ return (
+ <>
+
+
+
Questionnaire not found.
+
+ >
+ );
+ }
+
+ // Prepare fields to display
+ const fields = [
+ { name: 'Name', value: questionnaire.name || 'N/A' },
+ { name: 'Identifier', value: questionnaire.identifier || 'N/A' },
+ {
+ name: 'Interaction Type',
+ value: questionnaire.interactionType?.name || 'N/A',
+ },
+ {
+ name: 'Experiment',
+ value: questionnaire.experiment?.name || 'N/A',
+ },
+ {
+ name: 'Amount of Questions',
+ value: questionnaire.questions
+ ? questionnaire.questions.length.toString()
+ : 'N/A',
+ },
+ ];
+
+ return (
+ <>
+
+
+ {/* Title */}
+
Questionnaire View
+
+ {/* Questionnaire Details Component */}
+
+
+
+
+ >
+ );
+};
+
+export default Questionnaire;
\ No newline at end of file
diff --git a/src/pages/QuestionnaireCreation.tsx b/src/pages/QuestionnaireCreation.tsx
new file mode 100644
index 0000000..aa6c04f
--- /dev/null
+++ b/src/pages/QuestionnaireCreation.tsx
@@ -0,0 +1,176 @@
+import React, { useState, useEffect, useRef } from 'react';
+import { useNavigate, useParams } from 'react-router-dom';
+import Navbar from '../components/Navbar';
+import '../styles/QuestionnaireCreation.css';
+import { AddQuestionnaire } from '../types/questionnaire';
+import { addQuestionnaire } from '../services/questionnaireService';
+import { getAllInteractions } from '../services/interactionTypeService';
+import { InteractionType } from '../types/interactiontype';
+
+const QuestionnaireCreation: React.FC = () => {
+ const { id: experimentID } = useParams<{ id: string }>(); // Get experimentID from URL
+ const [name, setName] = useState('');
+ const [identifier, setIdentifier] = useState('');
+ const [interactionTypes, setInteractionTypes] = useState
([]);
+ const [interactionTypeID, setInteractionTypeID] = useState(null);
+ const [isDropdownOpen, setIsDropdownOpen] = useState(false);
+ const [interactionTypeError, setInteractionTypeError] = useState('');
+
+ const navigate = useNavigate();
+ const formRef = useRef(null);
+
+ useEffect(() => {
+ const fetchInteractionTypes = async () => {
+ try {
+ const interactionTypes: InteractionType[] = await getAllInteractions();
+ setInteractionTypes(interactionTypes);
+ } catch (error) {
+ console.error('Failed to fetch interaction types:', error);
+ }
+ };
+ fetchInteractionTypes();
+ }, []);
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+
+ let isValid = true;
+
+ // Reset custom error messages
+ setInteractionTypeError('');
+
+ // Validate Interaction Type
+ if (!interactionTypeID) {
+ setInteractionTypeError('Please select an interaction type.');
+ isValid = false;
+ }
+
+ // Trigger native validation messages
+ const form = formRef.current;
+ if (form && !form.checkValidity()) {
+ form.reportValidity();
+ isValid = false;
+ }
+
+ if (!isValid) {
+ return;
+ }
+
+ const questionnaireData: AddQuestionnaire = {
+ experimentID: experimentID!,
+ name: name,
+ identifier: identifier,
+ interactionTypeID: interactionTypeID!,
+ };
+
+ try {
+ const response = await addQuestionnaire(questionnaireData);
+ console.log('Questionnaire added successfully:', response);
+ navigate(`/QuestionnaireDashboard/${experimentID}`);
+ } catch (error) {
+ console.error('Error adding questionnaire:', error);
+ }
+ };
+
+ return (
+
+ {/* Navbar */}
+
+
+ {/* Main Container */}
+
+ {/* Title */}
+
New Questionnaire
+
+ {/* Content Box */}
+
+
+
+ );
+};
+
+export default QuestionnaireCreation;
diff --git a/src/pages/QuestionnaireDashboard.tsx b/src/pages/QuestionnaireDashboard.tsx
new file mode 100644
index 0000000..0e64d6d
--- /dev/null
+++ b/src/pages/QuestionnaireDashboard.tsx
@@ -0,0 +1,299 @@
+import React, { useEffect, useState } from 'react';
+import { useNavigate, useParams } from 'react-router-dom';
+import Navbar from '../components/Navbar';
+import '../styles/QuestionnaireDashboard.css';
+import { Questionnaire } from '../types/questionnaire';
+import { getQuestionnaireByExperimentID } from '../services/questionnaireService';
+import { Experiment } from '../types/experiment';
+import { getAllInteractions } from '../services/interactionTypeService';
+import { InteractionType } from '../types/interactiontype';
+
+const QuestionnaireDashboard: React.FC = () => {
+ const navigate = useNavigate();
+ const { id } = useParams<{ id: string }>();
+
+ const [questionnaires, setQuestionnaires] = useState([]);
+ const [experiment] = useState(null);
+ const [filteredQuestionnaires, setFilteredQuestionnaires] = useState([]);
+ const [searchQuery, setSearchQuery] = useState('');
+ const [isLoading, setIsLoading] = useState(true);
+
+ // Interaction Type Filter State
+ const [interactionTypes, setInteractionTypes] = useState([]);
+ const [selectedInteractionType, setSelectedInteractionType] = useState('All');
+ const [isDropdownOpen, setIsDropdownOpen] = useState(false);
+
+ // Sorting State
+ type SortKey = keyof Questionnaire | 'numberOfQuestions';
+ const [sortConfig, setSortConfig] = useState<{ key: SortKey; direction: string } | null>(null);
+
+ useEffect(() => {
+ const fetchQuestionnaires = async () => {
+ if (!id) return;
+ try {
+ setIsLoading(true);
+ const fetchedQuestionnaires = await getQuestionnaireByExperimentID(id);
+
+ // Process the data to ensure interactionType and questions are defined
+ const processedQuestionnaires = fetchedQuestionnaires.map((questionnaire) => ({
+ ...questionnaire,
+ interactionType: questionnaire.interactionType || { name: 'Unknown' },
+ questions: questionnaire.questions || [],
+ }));
+
+ setQuestionnaires(processedQuestionnaires);
+ setIsLoading(false);
+ } catch (error) {
+ console.error('Error fetching questionnaires:', error);
+ setIsLoading(false);
+ }
+ };
+
+ fetchQuestionnaires();
+ }, [id]);
+
+ // Fetch Interaction Types for the Filter
+ useEffect(() => {
+ const fetchInteractionTypes = async () => {
+ try {
+ const types = await getAllInteractions();
+ setInteractionTypes(types);
+ } catch (error) {
+ console.error('Failed to fetch interaction types:', error);
+ }
+ };
+ fetchInteractionTypes();
+ }, []);
+
+ useEffect(() => {
+ let filtered = [...questionnaires];
+
+ // Filter by Interaction Type
+ if (selectedInteractionType !== 'All') {
+ filtered = filtered.filter((q: Questionnaire) =>
+ q.interactionType?.name === selectedInteractionType
+ );
+ }
+
+ // Filter by search query
+ if (searchQuery) {
+ filtered = filtered.filter((q: Questionnaire) =>
+ q.name.toLowerCase().includes(searchQuery.toLowerCase())
+ );
+ }
+
+ // Apply sorting
+ if (sortConfig !== null) {
+ filtered.sort((a: Questionnaire, b: Questionnaire) => {
+ let aValue: any;
+ let bValue: any;
+
+ if (sortConfig.key === 'numberOfQuestions') {
+ aValue = a.questions ? a.questions.length : 0;
+ bValue = b.questions ? b.questions.length : 0;
+ } else if (sortConfig.key === 'interactionType') {
+ aValue = a.interactionType?.name || '';
+ bValue = b.interactionType?.name || '';
+ } else {
+ aValue = a[sortConfig.key] || '';
+ bValue = b[sortConfig.key] || '';
+ }
+
+ if (typeof aValue === 'string' && typeof bValue === 'string') {
+ aValue = aValue.toLowerCase();
+ bValue = bValue.toLowerCase();
+ }
+
+ if (aValue < bValue) {
+ return sortConfig.direction === 'ascending' ? -1 : 1;
+ }
+ if (aValue > bValue) {
+ return sortConfig.direction === 'ascending' ? 1 : -1;
+ }
+ return 0;
+ });
+ }
+
+ setFilteredQuestionnaires(filtered);
+ }, [searchQuery, sortConfig, questionnaires, selectedInteractionType]);
+
+ const requestSort = (key: SortKey) => {
+ let direction = 'ascending';
+ if (sortConfig && sortConfig.key === key && sortConfig.direction === 'ascending') {
+ direction = 'descending';
+ }
+ setSortConfig({ key, direction });
+ };
+
+ const getSortIconClass = (columnKey: SortKey) => {
+ if (sortConfig?.key === columnKey) {
+ return sortConfig.direction === 'ascending' ? 'sort-ascending' : 'sort-descending';
+ }
+ return '';
+ };
+
+ const navigateToCreateQuestionnaire = () => {
+ navigate(`/questionnairecreation/${id}`);
+ };
+
+ const handleQuestionnaireClick = (questionnaire: Questionnaire) => {
+ navigate(`/questionnaire/${id}`, { state: { questionnaire } });
+ };
+
+ const truncateText = (text: string, maxLength: number): string => {
+ if (text.length <= maxLength) return text;
+ return text.substring(0, maxLength) + '...';
+ };
+
+ return (
+
+ {/* Navbar */}
+
+
+ {/* Questionnaires Title */}
+
+ Questionnaires for Experiment: {truncateText(experiment?.name || `Experiment ${id}`, 35)}
+
+
+ {/* Filters Container */}
+
+ {/* Interaction Type Filter */}
+
+
+
+
+ {isDropdownOpen && (
+
+
{
+ setSelectedInteractionType('All');
+ setIsDropdownOpen(false);
+ }}
+ >
+ All
+
+ {interactionTypes.map((type) => (
+
{
+ setSelectedInteractionType(type.name);
+ setIsDropdownOpen(false);
+ }}
+ >
+ {type.name}
+
+ ))}
+
+ )}
+
+
+
+ {/* Search Filter */}
+
+
+
setSearchQuery(e.target.value)}
+ data-testid="search-input"
+ />
+
+
+
+
+
+ {/* Loading Animation or Questionnaires Table */}
+ {isLoading ? (
+
+ ) : (
+ <>
+ {/* Questionnaires Table */}
+
+
+ {/* Add Questionnaire Button */}
+
+ >
+ )}
+
+ );
+};
+
+export default QuestionnaireDashboard;
diff --git a/src/pages/__tests__/Dashboard.integration.test.tsx b/src/pages/__tests__/Dashboard.integration.test.tsx
index 1b2b1ba..2e0ee4c 100644
--- a/src/pages/__tests__/Dashboard.integration.test.tsx
+++ b/src/pages/__tests__/Dashboard.integration.test.tsx
@@ -1,101 +1,76 @@
// Dashboard.integration.test.tsx
import React from 'react';
-import { render, screen, waitFor, fireEvent, within } from '@testing-library/react';
-import Dashboard from '../Dashboard';
-import '@testing-library/jest-dom/extend-expect';
-import { getAllLivingLabs } from '../../services/livingLabService';
-import { getMyExperiments } from '../../services/experimentService';
+import { render, screen, fireEvent, waitFor, within } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
+import Dashboard from '../Dashboard';
+import {getAllLivingLabs} from '../../services/livingLabService';
+import { addExperiment, getMyExperiments } from '../../services/experimentService';
+import dotenv from 'dotenv';
-// Mock the API functions
-jest.mock('../../services/livingLabService', () => ({
- getAllLivingLabs: jest.fn(),
-}));
-
-jest.mock('../../services/experimentService', () => ({
- getMyExperiments: jest.fn(),
-}));
-
-const mockLivingLabs = [
- {
- ID: 'lab1',
- name: 'LivingLab 1',
- definition: [],
- $schema: '',
- },
- {
- ID: 'lab2',
- name: 'LivingLab 2',
- definition: [],
- $schema: '',
- },
-];
-
-const mockExperiments = [
- {
- ID: 'exp1',
- name: 'Experiment 1',
- start: '2023-01-01',
- end: '2023-12-31',
- user: { name: 'User 1' },
- livingLab: { name: 'LivingLab 1' },
- questionnaires: 5,
- messages: 10,
- responses: 100,
- },
- {
- ID: 'exp2',
- name: 'Experiment 2',
- start: '2023-02-01',
- end: '2023-11-30',
- user: { name: 'User 2' },
- livingLab: { name: 'LivingLab 2' },
- questionnaires: 3,
- messages: 5,
- responses: 50,
- },
-];
+// Load environment variables
+dotenv.config();
describe('Dashboard Integration Tests', () => {
- beforeEach(() => {
- jest.clearAllMocks();
- });
-
- // CATEGORY 1: DATA FETCHING AND INITIAL RENDERING
-
- test('FETCH LIVINGLABS AND EXPERIMENTS ON MOUNT', async () => {
- (getAllLivingLabs as jest.Mock).mockResolvedValueOnce(mockLivingLabs);
- (getMyExperiments as jest.Mock).mockResolvedValueOnce(mockExperiments);
+ // Generate a unique identifier for each test run
+ const uniqueId = new Date().toISOString().replace(/[:.]/g, '-');
+
+ // Store created experiment IDs and names
+ let createdExperimentIds: string[] = [];
+ let experimentNames: string[] = [];
+ let startDate: Date;
+ let endDate: Date;
+
+ // Use existing LivingLab
+ const existingLivingLabId = '9c7dbce1-6c4f-46b6-b0c6-ec5d2c2b48c1';
+ const existingLivingLabName = 'Living Lab Eindhoven';
+
+ beforeAll(async () => {
+ // Insert authentication token into localStorage
+ const authToken = process.env.AUTH_TOKEN;
+ if (authToken) {
+ window.localStorage.setItem('authToken', authToken);
+ } else {
+ console.warn('AUTH_TOKEN is not defined in the environment variables.');
+ }
- render(
-
-
-
- );
+ // Generate unique experiment names
+ experimentNames = [`Experiment 1 ${uniqueId}`, `Experiment 2 ${uniqueId}`];
- await waitFor(() => expect(getAllLivingLabs).toHaveBeenCalled());
- await waitFor(() => expect(getMyExperiments).toHaveBeenCalled());
+ // Calculate future start and end dates
+ const today = new Date();
+ startDate = new Date(today.getTime() + 24 * 60 * 60 * 1000); // Tomorrow
+ endDate = new Date(today.getTime() + 10 * 24 * 60 * 60 * 1000); // 10 days from today
- expect(await screen.findByText('Experiment 1')).toBeInTheDocument();
- expect(await screen.findByText('Experiment 2')).toBeInTheDocument();
- });
+ for (const name of experimentNames) {
+ const experimentData = {
+ name,
+ description: 'Sample experiment description',
+ start: startDate.toISOString(),
+ end: endDate.toISOString(),
+ // Removed unexpected properties
+ };
+ try {
+ const experiment = await addExperiment(experimentData);
+ createdExperimentIds.push(experiment.ID);
+ } catch (error) {
+ console.error('Failed to add experiment:', error);
+ }
+ }
+ });
- test('RENDER LOADING STATE', async () => {
- let resolveLivingLabs!: (value: any) => void;
- let resolveExperiments!: (value: any) => void;
-
- const livingLabsPromise = new Promise((resolve) => {
- resolveLivingLabs = resolve;
- });
+ afterAll(() => {
+ // Clear localStorage after tests
+ window.localStorage.clear();
+ });
- const experimentsPromise = new Promise((resolve) => {
- resolveExperiments = resolve;
- });
+ // CATEGORY 1: DATA FETCHING AND INITIAL RENDERING
- (getAllLivingLabs as jest.Mock).mockReturnValue(livingLabsPromise);
- (getMyExperiments as jest.Mock).mockReturnValue(experimentsPromise);
+ test('FETCH LIVINGLABS AND EXPERIMENTS ON MOUNT', async () => {
+ // Fetch data directly
+ const livingLabs = await getAllLivingLabs();
+ const myExperiments = await getMyExperiments();
render(
@@ -103,81 +78,45 @@ describe('Dashboard Integration Tests', () => {
);
- expect(screen.getByTestId('loading-container')).toBeInTheDocument();
-
- resolveLivingLabs(mockLivingLabs);
- resolveExperiments(mockExperiments);
-
- await waitFor(() => {
- expect(screen.queryByTestId('loading-container')).not.toBeInTheDocument();
- });
- });
-
- test('RENDER EXPERIMENTS TABLE AFTER FETCH', async () => {
- (getAllLivingLabs as jest.Mock).mockResolvedValueOnce(mockLivingLabs);
- (getMyExperiments as jest.Mock).mockResolvedValueOnce(mockExperiments);
-
- render(
-
-
-
- );
-
+ // Wait for the Dashboard to render
await waitFor(() => {
expect(screen.getByTestId('experiments-table')).toBeInTheDocument();
});
-
- expect(await screen.findByText('Experiment 1')).toBeInTheDocument();
- expect(await screen.findByText('Experiment 2')).toBeInTheDocument();
-
- // Use getAllByText and ensure you have the expected number of elements
- const livingLabCells = screen.getAllByText('LivingLab 1', { selector: 'td' });
- expect(livingLabCells).toHaveLength(1);
-
- const livingLab2Cells = screen.getAllByText('LivingLab 2', { selector: 'td' });
- expect(livingLab2Cells).toHaveLength(1);
- });
-
- // CATEGORY 2: FILTER COMBINATIONS
+ // Verify that the experiments from getMyExperiments are displayed
+ for (const experiment of myExperiments) {
+ expect(screen.getByText(experiment.name)).toBeInTheDocument();
+ }
- test('APPLYING MULTIPLE FILTERS SIMULTANEOUSLY', async () => {
- (getAllLivingLabs as jest.Mock).mockResolvedValueOnce(mockLivingLabs);
- (getMyExperiments as jest.Mock).mockResolvedValueOnce(mockExperiments);
+ // Verify that the living labs from getAllLivingLabs are available in the filters
+ fireEvent.click(screen.getByTestId('livinglab-dropdown-button'));
+ for (const lab of livingLabs) {
+ expect(screen.getByText(lab.name)).toBeInTheDocument();
+ }
+ });
+ test('RENDER LOADING STATE', async () => {
render(
);
+ // Initially, the loading container should be in the document
+ expect(screen.getByTestId('loading-container')).toBeInTheDocument();
+
+ // Wait for the experiments table to appear
await waitFor(() => {
expect(screen.getByTestId('experiments-table')).toBeInTheDocument();
});
- // LivingLab filter
- const livingLabDropdownButton = screen.getByTestId('livinglab-dropdown-button');
- fireEvent.click(livingLabDropdownButton);
- const livingLabOption = screen.getByTestId('livinglab-option-lab1');
- fireEvent.click(livingLabOption);
-
- // Date range filter
- const startDateInput = screen.getByTestId('start-date-input');
- const endDateInput = screen.getByTestId('end-date-input');
- fireEvent.change(startDateInput, { target: { value: '2023-01-01' } });
- fireEvent.change(endDateInput, { target: { value: '2023-12-31' } });
-
- // Search query
- const searchInput = screen.getByTestId('search-input');
- fireEvent.change(searchInput, { target: { value: 'Experiment 1' } });
-
- expect(screen.getByText('Experiment 1')).toBeInTheDocument();
- expect(screen.queryByText('Experiment 2')).not.toBeInTheDocument();
+ // Now, the loading container should not be in the document
+ expect(screen.queryByTestId('loading-container')).not.toBeInTheDocument();
});
- test('CLEARING FILTERS RESETS EXPERIMENTS LIST', async () => {
- (getAllLivingLabs as jest.Mock).mockResolvedValueOnce(mockLivingLabs);
- (getMyExperiments as jest.Mock).mockResolvedValueOnce(mockExperiments);
+ test('RENDER EXPERIMENTS TABLE AFTER FETCH', async () => {
+ // Fetch experiments
+ const myExperiments = await getMyExperiments();
render(
@@ -185,47 +124,23 @@ describe('Dashboard Integration Tests', () => {
);
+ // Wait for the experiments table to appear
await waitFor(() => {
expect(screen.getByTestId('experiments-table')).toBeInTheDocument();
});
- // Apply filters
- const livingLabDropdownButton = screen.getByTestId('livinglab-dropdown-button');
- fireEvent.click(livingLabDropdownButton);
- const livingLabOption = screen.getByTestId('livinglab-option-lab1');
- fireEvent.click(livingLabOption);
-
- const startDateInput = screen.getByTestId('start-date-input');
- const endDateInput = screen.getByTestId('end-date-input');
- fireEvent.change(startDateInput, { target: { value: '2023-01-01' } });
- fireEvent.change(endDateInput, { target: { value: '2023-12-31' } });
-
- const searchInput = screen.getByTestId('search-input');
- fireEvent.change(searchInput, { target: { value: 'Experiment 1' } });
-
- // Verify only Experiment 1 is shown
- expect(screen.getByText('Experiment 1')).toBeInTheDocument();
- expect(screen.queryByText('Experiment 2')).not.toBeInTheDocument();
-
- // Clear filters
- fireEvent.click(livingLabDropdownButton);
- const allLivingLabsOption = screen.getByTestId('livinglab-option-all');
- fireEvent.click(allLivingLabsOption);
-
- fireEvent.change(startDateInput, { target: { value: '' } });
- fireEvent.change(endDateInput, { target: { value: '' } });
- fireEvent.change(searchInput, { target: { value: '' } });
-
- // Verify both experiments are shown
- expect(screen.getByText('Experiment 1')).toBeInTheDocument();
- expect(screen.getByText('Experiment 2')).toBeInTheDocument();
+ // Verify that the experiments are displayed
+ for (const experiment of myExperiments) {
+ expect(screen.getByText(experiment.name)).toBeInTheDocument();
+ }
});
- // CATEGORY 3: SORTING AND FILTERING INTERACTION
+
+ // CATEGORY 2: SORTING AND FILTERING INTERACTION
test('SORTING APPLIES AFTER FILTERING', async () => {
- (getAllLivingLabs as jest.Mock).mockResolvedValueOnce(mockLivingLabs);
- (getMyExperiments as jest.Mock).mockResolvedValueOnce(mockExperiments);
+ // Fetch experiments
+ const myExperiments = await getMyExperiments();
render(
@@ -233,29 +148,36 @@ describe('Dashboard Integration Tests', () => {
);
- await waitFor(() => {
- expect(screen.getByTestId('experiments-table')).toBeInTheDocument();
- });
+ // Wait for experiments to be displayed
+ for (const experiment of myExperiments) {
+ expect(await screen.findByText(experiment.name)).toBeInTheDocument();
+ }
// Apply search filter
const searchInput = screen.getByTestId('search-input');
fireEvent.change(searchInput, { target: { value: 'Experiment' } });
- // Sort by 'Responses'
+ // Sort by 'Responses' (assuming 'Responses' is a sortable column)
const responsesHeader = screen.getByText('Responses');
fireEvent.click(responsesHeader);
+ // Verify the order of experiments
const experimentRows = screen.getAllByTestId(/experiment-row-/);
- const firstExperiment = within(experimentRows[0]).getByText('Experiment 2');
- const secondExperiment = within(experimentRows[1]).getByText('Experiment 1');
- expect(firstExperiment).toBeInTheDocument();
- expect(secondExperiment).toBeInTheDocument();
+ // Sort experiments manually to get expected order
+ const sortedExperiments = [...myExperiments]
+ .filter((exp) => exp.name.includes('Experiment'))
+ .sort((a, b) => (b.responses ?? 0) - (a.responses ?? 0));
+
+ for (let i = 0; i < sortedExperiments.length; i++) {
+ const row = experimentRows[i];
+ expect(within(row).getByText(sortedExperiments[i].name)).toBeInTheDocument();
+ }
});
test('FILTERING UPDATES SORTED LIST', async () => {
- (getAllLivingLabs as jest.Mock).mockResolvedValueOnce(mockLivingLabs);
- (getMyExperiments as jest.Mock).mockResolvedValueOnce(mockExperiments);
+ // Fetch experiments
+ const myExperiments = await getMyExperiments();
render(
@@ -263,9 +185,10 @@ describe('Dashboard Integration Tests', () => {
);
- await waitFor(() => {
- expect(screen.getByTestId('experiments-table')).toBeInTheDocument();
- });
+ // Wait for experiments to be displayed
+ for (const experiment of myExperiments) {
+ expect(await screen.findByText(experiment.name)).toBeInTheDocument();
+ }
// Sort by 'Responses'
const responsesHeader = screen.getByText('Responses');
@@ -273,100 +196,44 @@ describe('Dashboard Integration Tests', () => {
// Apply search filter
const searchInput = screen.getByTestId('search-input');
- fireEvent.change(searchInput, { target: { value: 'Experiment 1' } });
+ fireEvent.change(searchInput, { target: { value: experimentNames[0] } });
const experimentRows = screen.getAllByTestId(/experiment-row-/);
- const firstExperiment = within(experimentRows[0]).getByText('Experiment 1');
-
- expect(firstExperiment).toBeInTheDocument();
+ expect(within(experimentRows[0]).getByText(experimentNames[0])).toBeInTheDocument();
+ expect(experimentRows.length).toBe(1);
});
- // CATEGORY 4: NAVIGATION FUNCTIONALITY (NOT IMPLEMENTED YET!!!!)
-
- /* test('NAVIGATE TO CREATE EXPERIMENT PAGE', async () => {
- (getAllLivingLabs as jest.Mock).mockResolvedValueOnce(mockLivingLabs);
- (getMyExperiments as jest.Mock).mockResolvedValueOnce(mockExperiments);
-
- const mockNavigate = jest.fn();
- jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useNavigate: () => mockNavigate,
- }));
+ // CATEGORY 3: RESPONSIVENESS AND UI ELEMENTS
+ test('DROPDOWN CLICK STATES', async () => {
render(
);
+ // Wait for the component to render
await waitFor(() => {
- expect(screen.getByTestId('add-experiment-button')).toBeInTheDocument();
+ expect(screen.getByTestId('livinglab-dropdown-button')).toBeInTheDocument();
});
- const addButton = screen.getByTestId('add-experiment-button');
- fireEvent.click(addButton);
-
- expect(mockNavigate).toHaveBeenCalledWith('/experimentcreation');
- });
+ const dropdownButton = screen.getByTestId('livinglab-dropdown-button');
+ const dropdownIcon = screen.getByAltText('Dropdown Icon');
- test('NAVIGATE TO EXPERIMENT DETAILS PAGE', async () => {
- (getAllLivingLabs as jest.Mock).mockResolvedValueOnce(mockLivingLabs);
- (getMyExperiments as jest.Mock).mockResolvedValueOnce(mockExperiments);
+ expect(dropdownIcon).toHaveClass('dropdown-icon');
+ expect(dropdownIcon).not.toHaveClass('dropdown-icon-hover');
- const mockNavigate = jest.fn();
- jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useNavigate: () => mockNavigate,
- }));
+ fireEvent.click(dropdownButton);
+ expect(dropdownIcon).toHaveClass('dropdown-icon-hover');
- render(
-
-
-
- );
-
- await waitFor(() => {
- expect(screen.getByTestId('experiments-table')).toBeInTheDocument();
- });
-
- const exp1Row = screen.getByTestId('experiment-row-exp1');
- fireEvent.click(exp1Row);
-
- expect(mockNavigate).toHaveBeenCalledWith('/experiment/exp1');
+ fireEvent.click(dropdownButton);
+ expect(dropdownIcon).not.toHaveClass('dropdown-icon-hover');
});
- */
-
- // CATEGORY 5: RESPONSIVENESS AND UI ELEMENTS
-
- test('DROPDOWN CLICK STATES', async () => {
- // Mock API calls
- (getAllLivingLabs as jest.Mock).mockResolvedValueOnce(mockLivingLabs);
- (getMyExperiments as jest.Mock).mockResolvedValueOnce(mockExperiments);
-
- // Render the Dashboard component
- render(
-
-
-
- );
-
- const dropdownButton = screen.getByTestId('livinglab-dropdown-button');
-
- const dropdownIcon = screen.getByAltText('Dropdown Icon');
- expect(dropdownIcon).toHaveClass('dropdown-icon');
- expect(dropdownIcon).not.toHaveClass('dropdown-icon-hover');
-
- fireEvent.click(dropdownButton);
- expect(dropdownIcon).toHaveClass('dropdown-icon-hover');
-
- fireEvent.click(dropdownButton);
- expect(dropdownIcon).not.toHaveClass('dropdown-icon-hover');
-});
test('ALTERNATING ROW COLORS', async () => {
- (getAllLivingLabs as jest.Mock).mockResolvedValueOnce(mockLivingLabs);
- (getMyExperiments as jest.Mock).mockResolvedValueOnce(mockExperiments);
+ // Fetch experiments
+ const myExperiments = await getMyExperiments();
render(
@@ -374,21 +241,21 @@ describe('Dashboard Integration Tests', () => {
);
- await waitFor(() => {
- expect(screen.getByTestId('experiments-table')).toBeInTheDocument();
- });
+ // Wait for experiments to be displayed
+ for (const experiment of myExperiments) {
+ expect(await screen.findByText(experiment.name)).toBeInTheDocument();
+ }
const experimentRows = screen.getAllByTestId(/experiment-row-/);
expect(experimentRows[0]).toHaveClass('row-even');
expect(experimentRows[1]).toHaveClass('row-odd');
});
-
- // CATEGORY 6: STATE MANAGEMENT AND SIDE EFFECTS
+ // CATEGORY 4: STATE MANAGEMENT AND SIDE EFFECTS
test('USEEFFECT DEPENDENCIES TRIGGER CORRECTLY', async () => {
- (getAllLivingLabs as jest.Mock).mockResolvedValueOnce(mockLivingLabs);
- (getMyExperiments as jest.Mock).mockResolvedValueOnce(mockExperiments);
+ // Fetch experiments
+ const myExperiments = await getMyExperiments();
render(
@@ -396,48 +263,15 @@ describe('Dashboard Integration Tests', () => {
);
- await waitFor(() => {
- expect(screen.getByTestId('experiments-table')).toBeInTheDocument();
- });
+ // Wait for experiments to be displayed
+ expect(await screen.findByText(experimentNames[0])).toBeInTheDocument();
+ // Change search input
const searchInput = screen.getByTestId('search-input');
- fireEvent.change(searchInput, { target: { value: 'Experiment 1' } });
-
- expect(screen.getByText('Experiment 1')).toBeInTheDocument();
- expect(screen.queryByText('Experiment 2')).not.toBeInTheDocument();
- });
-
- test('SELECT ALL STATE REFLECTS INDIVIDUAL SELECTIONS', async () => {
- (getAllLivingLabs as jest.Mock).mockResolvedValueOnce(mockLivingLabs);
- (getMyExperiments as jest.Mock).mockResolvedValueOnce(mockExperiments);
-
- render(
-
-
-
- );
-
- await waitFor(() => {
- expect(screen.getByTestId('experiments-table')).toBeInTheDocument();
- });
-
- const selectAllCheckbox = screen.getByTestId('select-all-checkbox');
- const exp1Checkbox = screen.getByTestId('experiment-checkbox-exp1');
- const exp2Checkbox = screen.getByTestId('experiment-checkbox-exp2');
-
- // Initially, select all is unchecked
- expect(selectAllCheckbox).not.toBeChecked();
-
- // Select all
- fireEvent.click(selectAllCheckbox);
- expect(selectAllCheckbox).toBeChecked();
- expect(exp1Checkbox).toBeChecked();
- expect(exp2Checkbox).toBeChecked();
+ fireEvent.change(searchInput, { target: { value: experimentNames[0] } });
- // Uncheck one experiment
- fireEvent.click(exp1Checkbox);
- expect(selectAllCheckbox).not.toBeChecked();
- expect(exp1Checkbox).not.toBeChecked();
- expect(exp2Checkbox).toBeChecked();
+ // Verify that only the searched experiment is displayed
+ expect(screen.getByText(experimentNames[0])).toBeInTheDocument();
+ expect(screen.queryByText(experimentNames[1])).not.toBeInTheDocument();
});
-});
+});
\ No newline at end of file
diff --git a/src/pages/__tests__/Dashboard.unit.test.tsx b/src/pages/__tests__/Dashboard.unit.test.tsx
index 8add7ca..0105685 100644
--- a/src/pages/__tests__/Dashboard.unit.test.tsx
+++ b/src/pages/__tests__/Dashboard.unit.test.tsx
@@ -70,12 +70,11 @@ describe('Dashboard Component - Unit Tests', () => {
$schema: '',
} as User,
livingLab: mockLivingLabs[1],
- responses: 5,
+ responses: 8,
numberOfQuestionnaires: 2,
numberOfMessages: 10,
messageActivity: 5,
questionnaireActivity: 3,
- $schema: '',
},
{
ID: '2',
@@ -94,9 +93,8 @@ describe('Dashboard Component - Unit Tests', () => {
responses: 15,
numberOfQuestionnaires: 5,
numberOfMessages: 20,
- messageActivity: 5,
- questionnaireActivity: 3,
- $schema: '',
+ messageActivity: 10,
+ questionnaireActivity: 5,
},
{
ID: '3',
@@ -117,7 +115,6 @@ describe('Dashboard Component - Unit Tests', () => {
numberOfMessages: 15,
messageActivity: 5,
questionnaireActivity: 3,
- $schema: '',
},
];
@@ -311,6 +308,7 @@ describe('Dashboard Component - Unit Tests', () => {
// Wait for the component to re-render and get the updated experiment rows
await waitFor(() => {
+ screen.debug();
const table = screen.getByTestId('experiments-table');
const experimentRows = within(table).getAllByTestId(/experiment-row-/);
@@ -320,102 +318,8 @@ describe('Dashboard Component - Unit Tests', () => {
});
- // CATEGORY 5: CHECKBOX SELECTION
- describe('CATEGORY 5: CHECKBOX SELECTION', () => {
- test('INDIVIDUAL CHECKBOX RENDERING', async () => {
- render(
-
-
-
- );
-
- // Wait for experiments to be displayed
- await waitFor(() => expect(screen.getByText('Past Experiment')).toBeInTheDocument());
-
- // Check that each experiment row has a checkbox
- mockExperiments.forEach(exp => {
- const checkbox = screen.getByTestId(`experiment-checkbox-${exp.ID}`);
- expect(checkbox).toBeInTheDocument();
- expect(checkbox).not.toBeChecked();
- });
- });
-
- test('INDIVIDUAL CHECKBOX TOGGLES STATE', async () => {
- render(
-
-
-
- );
-
- // Wait for experiments to be displayed
- await waitFor(() => expect(screen.getByText('Past Experiment')).toBeInTheDocument());
-
- const checkbox = screen.getByTestId('experiment-checkbox-1');
-
- // Initially unchecked
- expect(checkbox).not.toBeChecked();
-
- // Click to check
- fireEvent.click(checkbox);
- expect(checkbox).toBeChecked();
-
- // Click again to uncheck
- fireEvent.click(checkbox);
- expect(checkbox).not.toBeChecked();
- });
-
- test('SELECT ALL CHECKBOX RENDERS CORRECTLY', async () => {
- render(
-
-
-
- );
-
- // Wait for the select all checkbox to be in the document
- await waitFor(() => expect(screen.getByTestId('select-all-checkbox')).toBeInTheDocument());
-
- const selectAllCheckbox = screen.getByTestId('select-all-checkbox');
-
- // Initially unchecked
- expect(selectAllCheckbox).not.toBeChecked();
- });
-
- test('SELECT ALL CHECKBOX TOGGLES ALL EXPERIMENTS', async () => {
- render(
-
-
-
- );
-
- // Wait for experiments and select all checkbox to be displayed
- await waitFor(() => expect(screen.getByTestId('select-all-checkbox')).toBeInTheDocument());
-
- const selectAllCheckbox = screen.getByTestId('select-all-checkbox');
-
- // Click to check all
- fireEvent.click(selectAllCheckbox);
- expect(selectAllCheckbox).toBeChecked();
-
- // All individual checkboxes should be checked
- mockExperiments.forEach(exp => {
- const checkbox = screen.getByTestId(`experiment-checkbox-${exp.ID}`);
- expect(checkbox).toBeChecked();
- });
-
- // Click again to uncheck all
- fireEvent.click(selectAllCheckbox);
- expect(selectAllCheckbox).not.toBeChecked();
-
- // All individual checkboxes should be unchecked
- mockExperiments.forEach(exp => {
- const checkbox = screen.getByTestId(`experiment-checkbox-${exp.ID}`);
- expect(checkbox).not.toBeChecked();
- });
- });
- });
-
- // CATEGORY 6: GETSTATUS FUNCTION
- describe('CATEGORY 6: GETSTATUS FUNCTION', () => {
+ // CATEGORY 5: GETSTATUS FUNCTION
+ describe('CATEGORY 5: GETSTATUS FUNCTION', () => {
test("Displays 'Upcoming' status for future experiments", async () => {
render(
diff --git a/src/services/interactionService.ts b/src/services/interactionService.ts
new file mode 100644
index 0000000..83ea844
--- /dev/null
+++ b/src/services/interactionService.ts
@@ -0,0 +1,36 @@
+import { InteractionType } from '../types/interactiontype';
+const API_URL = 'https://wildlifenl-uu-michi011.apps.cl01.cp.its.uu.nl/interactions/';
+
+
+const getAuthToken = (): string | null => {
+ return localStorage.getItem('authToken');
+};
+
+export const getAllInteractions = async (): Promise => {
+ try {
+ const token = getAuthToken();
+ if (!token) {
+ throw new Error('No authentication token found');
+ }
+
+ const response = await fetch(`${API_URL}`, {
+ method: 'GET',
+ headers: {
+ Accept: 'application/json',
+ Authorization: `Bearer ${token}`,
+ },
+ });
+
+ if (response.ok) {
+ const data = await response.json();
+ return data;
+ } else {
+ const errorData = await response.json();
+ console.error('Failed to fetch interaction types:', errorData);
+ throw new Error('Failed to fetch interaction types');
+ }
+ } catch (error) {
+ console.error('Error fetching interaction types:', error);
+ throw error;
+ }
+};
\ No newline at end of file
diff --git a/src/services/interactionTypeService.ts b/src/services/interactionTypeService.ts
new file mode 100644
index 0000000..34e0409
--- /dev/null
+++ b/src/services/interactionTypeService.ts
@@ -0,0 +1,36 @@
+import { InteractionType } from '../types/interactiontype';
+const API_URL = 'https://wildlifenl-uu-michi011.apps.cl01.cp.its.uu.nl/interactionTypes/';
+
+
+const getAuthToken = (): string | null => {
+ return localStorage.getItem('authToken');
+};
+
+export const getAllInteractions = async (): Promise => {
+ try {
+ const token = getAuthToken();
+ if (!token) {
+ throw new Error('No authentication token found');
+ }
+
+ const response = await fetch(`${API_URL}`, {
+ method: 'GET',
+ headers: {
+ Accept: 'application/json',
+ Authorization: `Bearer ${token}`,
+ },
+ });
+
+ if (response.ok) {
+ const data = await response.json();
+ return data;
+ } else {
+ const errorData = await response.json();
+ console.error('Failed to fetch interaction types:', errorData);
+ throw new Error('Failed to fetch interaction types');
+ }
+ } catch (error) {
+ console.error('Error fetching interaction types:', error);
+ throw error;
+ }
+};
\ No newline at end of file
diff --git a/src/services/questionnaireService.ts b/src/services/questionnaireService.ts
new file mode 100644
index 0000000..5d23065
--- /dev/null
+++ b/src/services/questionnaireService.ts
@@ -0,0 +1,70 @@
+import {AddQuestionnaire, Questionnaire} from '../types/questionnaire'
+const QUESTIONNAIRE_API_URL = 'https://wildlifenl-uu-michi011.apps.cl01.cp.its.uu.nl/questionnaire/';
+const QUESTIONNAIRES_API_URL = 'https://wildlifenl-uu-michi011.apps.cl01.cp.its.uu.nl/questionnaires/';
+
+
+const getAuthToken = (): string | null => {
+ return localStorage.getItem('authToken');
+};
+
+export const addQuestionnaire = async (questionnaireData: AddQuestionnaire): Promise => {
+ try {
+ const token = getAuthToken();
+ if (!token) {
+ throw new Error('No authentication token found');
+ }
+
+ const response = await fetch(`${QUESTIONNAIRE_API_URL}`, {
+ method: 'POST',
+ headers: {
+ Accept: 'application/json, application/problem+json',
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${token}`,
+ },
+ body: JSON.stringify(questionnaireData),
+ });
+
+ console.log('Add Experiment response:', response);
+
+ if (response.ok) {
+ const data = await response.json();
+ return data;
+ } else {
+ const errorData = await response.json();
+ console.error('Failed to add experiment:', errorData);
+ throw new Error('Failed to add experiment');
+ }
+ } catch (error) {
+ console.error('Add Experiment error:', error);
+ throw error;
+ }
+};
+
+export const getQuestionnaireByExperimentID = async (id: string): Promise => {
+ try {
+ const token = getAuthToken();
+ if (!token) {
+ throw new Error('No authentication token found');
+ }
+
+ const response = await fetch(`${QUESTIONNAIRES_API_URL}${id}`, {
+ method: 'GET',
+ headers: {
+ Accept: 'application/json, application/problem+json',
+ Authorization: `Bearer ${token}`,
+ },
+ });
+
+ if (!response.ok) {
+ const errorText = await response.text();
+ throw new Error(`Failed to fetch messages: ${errorText}`);
+ }
+
+ const data = await response.json();
+ return data as Questionnaire[];
+ } catch (error) {
+ console.error('Error fetching messages:', error);
+ throw error;
+ }
+};
+
diff --git a/src/services/speciesService.ts b/src/services/speciesService.ts
new file mode 100644
index 0000000..2da3bd3
--- /dev/null
+++ b/src/services/speciesService.ts
@@ -0,0 +1,38 @@
+import { Species } from "../types/species";
+const API_URL = 'https://wildlifenl-uu-michi011.apps.cl01.cp.its.uu.nl/species/';
+
+
+const getAuthToken = (): string | null => {
+ return localStorage.getItem('authToken');
+};
+
+export const getAllSpecies = async (): Promise => {
+ try {
+ const token = getAuthToken();
+ if (!token) {
+ throw new Error('No authentication token found');
+ }
+
+ const response = await fetch(`${API_URL}`, {
+ method: 'GET',
+ headers: {
+ Accept: 'application/json, application/problem+json',
+ Authorization: `Bearer ${token}`,
+ },
+ });
+
+ console.log('Get All Species response:', response);
+
+ if (response.ok) {
+ const data = await response.json();
+ return data;
+ } else {
+ const errorData = await response.json();
+ console.error('Failed to fetch all species:', errorData);
+ throw new Error('Failed to fetch all species');
+ }
+ } catch (error) {
+ console.error('Get All Species error:', error);
+ throw error;
+ }
+};
\ No newline at end of file
diff --git a/src/services/triggerTypeService.ts b/src/services/triggerTypeService.ts
new file mode 100644
index 0000000..4b5b2c7
--- /dev/null
+++ b/src/services/triggerTypeService.ts
@@ -0,0 +1,37 @@
+const API_URL = 'https://wildlifenl-uu-michi011.apps.cl01.cp.its.uu.nl/triggerTypes/';
+
+
+const getAuthToken = (): string | null => {
+ return localStorage.getItem('authToken');
+};
+
+export const getAllTriggerTypes = async (): Promise => {
+ try {
+ const token = getAuthToken();
+ if (!token) {
+ throw new Error('No authentication token found');
+ }
+
+ const response = await fetch(`${API_URL}`, {
+ method: 'GET',
+ headers: {
+ Accept: 'application/json, application/problem+json',
+ Authorization: `Bearer ${token}`,
+ },
+ });
+
+ console.log('Get All Trigger Types response:', response);
+
+ if (response.ok) {
+ const data: string[] = await response.json();
+ return data;
+ } else {
+ const errorData = await response.json();
+ console.error('Failed to fetch all trigger types:', errorData);
+ throw new Error('Failed to fetch all trigger types');
+ }
+ } catch (error) {
+ console.error('Get All Trigger Types error:', error);
+ throw error;
+ }
+};
\ No newline at end of file
diff --git a/src/styles/Dashboard.css b/src/styles/Dashboard.css
index dde39ee..711a8e8 100644
--- a/src/styles/Dashboard.css
+++ b/src/styles/Dashboard.css
@@ -2,7 +2,9 @@
.dashboard-container {
font-family: 'Roboto', sans-serif;
position: relative;
- overflow-x: hidden;
+ display: flex;
+ flex-direction: column;
+ /* Removed overflow-x to enable scrollbar */
}
/* Experiments Title */
@@ -13,6 +15,7 @@
margin-top: 100px; /* Adjust for navbar height */
margin-left: 24px;
user-select: none;
+ flex-shrink: 0; /* Prevent shrinking */
}
/* Filters Container */
@@ -22,7 +25,8 @@
align-items: center;
margin-top: 20px;
margin-left: 5%;
- margin-right: 5%; /* Added right margin to align with left */
+ margin-right: 5%; /* Align with left margin */
+ flex-shrink: 0; /* Prevent shrinking */
}
/* Filter Styles */
@@ -155,7 +159,7 @@
padding-right: 40px; /* Prevent text overlap with icon */
font-size: 25px;
font-weight: 300;
- color: rgba(0, 0, 0, 0);
+ color: rgba(0, 0, 0, 0); /* Hidden text */
border: 3px solid rgba(0, 0, 0, 0.25);
border-radius: 12px;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.25);
@@ -167,6 +171,10 @@
box-shadow: 0 0 5px #27A966;
}
+.search-input input[type="text"]:focus {
+ color: #000; /* Reveal text on focus */
+}
+
.search-icon {
position: absolute;
right: 10px;
@@ -180,12 +188,15 @@
/* Table Container */
.table-container {
margin-top: 20px;
- margin-left: 5%; /* Added margins to prevent touching edges */
+ margin-left: 5%; /* Margins to prevent touching edges */
margin-right: 5%;
overflow-x: auto;
+ overflow-y: auto; /* Enables vertical scrolling */
+ height: calc(100vh - 270px); /* Adjusted total offset */
user-select: none;
}
+/* Experiments Table */
.experiments-table {
width: 100%;
border-collapse: collapse;
@@ -193,7 +204,7 @@
.experiments-table th,
.experiments-table td {
- padding: 10px;
+ padding: 12px;
text-align: center;
}
@@ -250,7 +261,6 @@
background-color: rgba(217, 217, 217, 0.40);
}
-
/* Add Experiment Button */
.add-experiment-button {
position: fixed;
@@ -262,6 +272,7 @@
border: none;
padding: 0;
cursor: pointer;
+ z-index: 1000; /* Ensure it appears above other elements */
}
.add-experiment-button img {
@@ -269,6 +280,7 @@
height: auto;
}
+/* Loading Container */
.loading-container {
display: flex;
justify-content: center;
@@ -289,3 +301,62 @@
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
+
+/* Scrollbar Styling (Optional) */
+.table-container::-webkit-scrollbar {
+ width: 8px; /* Increased width for better visibility */
+}
+
+.table-container::-webkit-scrollbar-track {
+ background: #f1f1f1;
+ border-radius: 4px;
+}
+
+.table-container::-webkit-scrollbar-thumb {
+ background: #888;
+ border-radius: 4px;
+}
+
+.table-container::-webkit-scrollbar-thumb:hover {
+ background: #555;
+}
+
+/* Responsive Adjustments (Optional) */
+@media (max-width: 768px) {
+ .experiments-title {
+ font-size: 36px;
+ margin-top: 70px;
+ margin-left: 16px;
+ }
+
+ .filter-label {
+ font-size: 18px;
+ }
+
+ .dropdown-button {
+ height: 45px;
+ font-size: 18px;
+ }
+
+ .search-input input[type="text"] {
+ width: 250px;
+ height: 45px;
+ font-size: 20px;
+ }
+
+ .search-icon {
+ width: 25px;
+ height: 25px;
+ }
+
+ .experiments-table th,
+ .experiments-table td {
+ padding: 8px;
+ font-size: 14px;
+ }
+
+ .add-experiment-button {
+ width: 90px;
+ height: 90px;
+ }
+}
diff --git a/src/styles/DynamicView.css b/src/styles/DynamicView.css
new file mode 100644
index 0000000..8b7a950
--- /dev/null
+++ b/src/styles/DynamicView.css
@@ -0,0 +1,75 @@
+.dynamic-view-container {
+ /* Remove fixed width to allow dynamic sizing */
+ border: 1px solid #000;
+ background: rgba(217, 217, 217, 0.0);
+ padding: 16px;
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+ width: fit-content; /* Allows the width to adjust based on content */
+ height: fit-content;
+ max-width: 100%; /* Prevents the container from exceeding the viewport width */
+ overflow-y: auto;
+ max-height: 62vh;
+}
+
+.dynamic-view-field {
+ margin-bottom: 16px;
+}
+
+.dynamic-view-field-content {
+ display: flex;
+ align-items: center;
+}
+
+.dynamic-view-field-name {
+ width: 550px; /* You can adjust or remove this fixed width */
+ color: #000;
+ font-family: Roboto;
+ font-size: 25px;
+ font-weight: 500;
+ letter-spacing: 0.75px;
+}
+
+.dynamic-view-field-value {
+ width: 816px; /* You can adjust or remove this fixed width */
+ color: #0F0F0F;
+ font-family: Roboto;
+ font-size: 25px;
+ font-weight: 400;
+ letter-spacing: 0.75px;
+ margin-left: 16px;
+}
+
+.dynamic-view-line {
+ width: 100%; /* Makes the line span the full width of the container */
+ height: 3px;
+ background-color: #00000058;
+ margin-top: 8px;
+}
+
+.dynamic-view-field-value {
+ height: auto; /* Allows dynamic height based on content */
+ overflow-wrap: break-word;
+ word-break: break-word;
+ white-space: pre-wrap;
+}
+
+/* Scrollbar Styling (Optional) */
+.dynamic-view-container::-webkit-scrollbar {
+ width: 8px;
+}
+
+.dynamic-view-container::-webkit-scrollbar-track {
+ background: #f1f1f1;
+ border-radius: 4px;
+}
+
+.dynamic-view-container::-webkit-scrollbar-thumb {
+ background: #888;
+ border-radius: 4px;
+}
+
+.dynamic-view-container::-webkit-scrollbar-thumb:hover {
+ background: #555;
+}
\ No newline at end of file
diff --git a/src/styles/Experiment.css b/src/styles/Experiment.css
index 5ad50ea..563356f 100644
--- a/src/styles/Experiment.css
+++ b/src/styles/Experiment.css
@@ -1,4 +1,3 @@
-/* Reset and base styles */
* {
margin: 0;
padding: 0;
@@ -9,11 +8,11 @@ body {
font-family: 'Roboto', sans-serif;
}
+/* Experiment Container */
.experiment-container {
- position: relative;
- max-width: 1920px;
- margin: 0 auto;
- width: 100%;
+ max-width: 1200px; /* Set a max width */
+ margin: 0 auto; /* Center the container */
+ padding: 20px; /* Add padding for spacing */
}
/* Title styling */
@@ -34,330 +33,125 @@ body {
/* Experiment Details Component */
.experiment-details-component {
- position: absolute;
- top: 160px;
- left: 12%;
- width: 1300px;
- height: 470px;
-
- border: 1px solid #000;
- background: rgba(217, 217, 217, 0);
- display: flex;
- flex-direction: column;
-}
-
-/* Experiment Title Component */
-.experiment-title-comp {
- position: relative;
- margin-top: 25px;
- margin-left: 142px;
- width: 1040px;
- height: 105px;
-}
-
-.experiment-title-text {
- position: absolute;
- top: 0;
- left: 4px;
- width: 250px;
- height: 36px;
-
- color: #000;
- font-family: 'Roboto', sans-serif;
- font-size: 27px;
- font-weight: 300;
- line-height: normal;
-}
-
-.experiment-textbar {
- position: absolute;
- top: 50px;
- left: 0;
- width: 1040px;
- height: 56px;
-
- border-radius: 12px;
- border: 3px solid rgba(0, 0, 0, 0.651);
- background: rgba(196, 196, 196, 0);
- box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.25);
+ width: 100%;
+ margin-top: 160px;
}
-.experiment-textbar-content {
- margin: 13px 15px 17px 15px;
- width: 723px;
- color: #000;
- font-family: 'Roboto', sans-serif;
- font-size: 22px;
- font-weight: 500;
- line-height: normal;
-}
-
-/* Description Component */
-.description-comp {
- position: relative;
- margin-top: 25px;
- margin-left: 142px;
- width: 1040px;
- height: 185px;
-}
-
-.description-text {
- position: absolute;
- top: 0;
- left: 4px;
- width: 250px;
- height: 36px;
-
- color: #000;
- font-family: 'Roboto', sans-serif;
- font-size: 27px;
- font-weight: 300;
- line-height: normal;
-}
-
-.description-textbar {
- position: absolute;
- top: 50px;
- left: 0;
- width: 1040px;
- height: 136px;
-
- border-radius: 12px;
- border: 3px solid rgba(0, 0, 0, 0.651);
- background: rgba(196, 196, 196, 0);
- box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.25);
-}
-
-.description-textbar-content {
- margin: 13px 15px 17px 15px;
- width: 985px;
- height: 100px;
-
- color: #000;
- font-family: 'Roboto', sans-serif;
- font-size: 20px;
- font-weight: 500;
- line-height: normal;
-}
-
-/* Duration Set Component */
-.duration-set-comp {
- position: absolute;
- top: 360px;
- left: 146px;
- width: 543px;
- height: 93px;
-}
-
-.duration-text {
- position: absolute;
- top: 0;
- left: 8px;
- width: 220px;
- height: 23px;
-
- color: #000;
- text-align: center;
- font-family: 'Roboto', sans-serif;
- font-size: 20px;
- font-weight: 400;
- letter-spacing: 0.6px;
-}
-
-.time-set {
- position: absolute;
- top: 43px;
- left: 0;
+/* Buttons Container */
+.buttons-container {
display: flex;
+ justify-content: center;
align-items: center;
+ gap: 20px;
+ margin-top: 23px; /* Spacing above the buttons */
}
-.time-set-box {
- width: 250px;
- height: 48px;
-
- border-radius: 12px;
- border: 3px solid rgba(0, 0, 0, 0.651);
- background: rgba(196, 196, 196, 0);
- box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.25);
+/* View Questionnaires Button */
+.view-questionnaires-button {
+ width: 500px;
+ height: 91px;
+ flex-shrink: 0;
+ border-radius: 19px;
+ background-color: #27A966;
+ position: relative;
display: flex;
align-items: center;
- padding-left: 27px;
-
- color: #000;
- font-family: 'Roboto', sans-serif;
- font-size: 22px;
- font-weight: 500;
-}
-
-.date-separator {
- margin-right: 10px;
- font-size: 20px;
- font-weight: bold;
+ padding-left: 45px;
+ padding-right: 111px; /* Ensures the text ends 111px from the right edge */
+ border: none;
+ cursor: pointer;
}
-/* Location Set Component */
-.location-set-comp {
- position: absolute;
- top: 360px;
- left: 725px;
- width: 250px;
- height: 93px; /* Updated height to match .duration-set-comp */
+.view-questionnaires-button:hover {
+ background-color: #1e8a52;
}
-.location-text {
- position: absolute;
- top: 0px;
- right: 22px;
- width: 250px;
- height: 45px;
-
- color: #000;
- text-align: center;
+/* Text Inside the Button */
+.view-questionnaires-button-text {
+ color: #FFF;
font-family: 'Roboto', sans-serif;
- font-size: 20px;
- font-weight: 400;
- letter-spacing: 0.6px;
+ font-size: 34px;
+ font-weight: 500;
+ letter-spacing: 1.35px;
+ line-height: normal;
}
-.location-box {
+/* Icon Inside the Button */
+.view-questionnaires-button-icon {
+ width: 46px;
+ height: 52px;
position: absolute;
- top: 43px;
- left: 0;
- width: 250px;
- height: 48px;
-
- border-radius: 12px;
- border: 3px solid rgba(0, 0, 0, 0.651);
- background: rgba(196, 196, 196, 0);
- box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.25);
+ right: 30px;
+ top: 19px;
+ fill: #FFF;
+}
+/* View Messages Button */
+.view-messages-button {
+ width: 415px;
+ height: 91px;
+ flex-shrink: 0;
+ border-radius: 19px;
+ background-color: #27A966;
+ position: relative;
display: flex;
align-items: center;
- padding: 0 15px; /* Even padding on both sides */
- overflow: hidden; /* Prevent overflow */
+ padding-left: 45px;
+ padding-right: 111px;
+ border: none;
+ cursor: pointer;
}
-.location-box-text {
- flex: 1;
- color: #000;
- font-family: 'Roboto', sans-serif;
- font-size: clamp(16px, 5vw, 25px);
- font-weight: 400;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- text-align: left;
- line-height: 48px;
+.view-messages-button:hover {
+ background-color: #1e8a52;
}
-/* Stop Experiment Button */
-.stop-experiment-button {
- position: absolute;
- top: 395px;
- left: 1015px;
- width: 250px;
- height: 62px;
-
- border-radius: 12px;
- background: #E81E21;
- box-shadow: 0px 4px 10px rgba(233, 68, 75, 0.25);
-
+/* Text Inside the Button */
+.view-messages-button-text {
color: #FFF;
- text-align: center;
font-family: 'Roboto', sans-serif;
- font-size: 30px;
+ font-size: 34px;
font-weight: 500;
- letter-spacing: 0.9px;
- line-height: 62px; /* Center text vertically */
- border: none;
- cursor: pointer;
+ letter-spacing: 1.35px;
+ line-height: normal;
}
-
-/* Buttons Container */
-.buttons-container {
+/* Icon Inside the Button */
+.view-messages-button-icon {
+ width: 46px;
+ height: 52px;
position: absolute;
- top: 600px; /* Adjust as needed */
- width: 100%;
- display: flex;
- justify-content: space-between;
- padding: 38px 300px;
+ right: 30px; /* 30px from the right edge */
+ top: 23px; /* 19px from the top edge */
+ fill: #FFF; /* For SVG icons */
}
-/* Common Styles for Overview Buttons */
-.overview-button {
- display: flex;
- flex-direction: column;
- width: 445px;
- height: 147px;
+/* Stop Experiment Button */
+.stop-experiment-button {
+ width: 468px;
+ height: 91px;
flex-shrink: 0;
- border-radius: 14px;
- border: 1px solid #000;
- background-color: #ffffff;
+ border-radius: 19px;
+ background-color: #E81E21;
position: relative;
- padding: 0;
- margin: 0;
- cursor: pointer;
- text-align: left;
- -webkit-appearance: none;
- -moz-appearance: none;
- appearance: none;
-}
-
-.overview-button:focus {
- outline: none;
-}
-
-.overview-button .button-top {
- width: 445px;
- height: 64px;
- border-radius: 13px 13px 0 0;
- background: #27A966;
display: flex;
align-items: center;
- position: relative;
+ justify-content: center;
+ border: none;
+ cursor: pointer;
}
/* Text Inside the Top Section */
-.button-text {
- margin-left: 21px;
+.stop-button-text {
color: #FFF;
font-family: 'Roboto', sans-serif;
- font-size: 32px;
+ font-size: 34px;
font-weight: 500;
letter-spacing: 0.95px;
}
-/* Icon Inside the Top Section */
-.questionnaire-overview-button .button-icon {
- position: absolute;
- top: 13px;
- left: 267px;
- width: 32px;
- height: 36px;
-}
-
-.message-overview-button .button-icon {
- position: absolute;
- top: 20px;
- left: 194px;
- width: 27px;
- height: 27px;
-}
-
-/* Content Section of the Button */
-.button-content {
- position: absolute;
- top: 80px;
- left: 14px;
- width: 350px;
- height: 49px;
- color: #000;
- font-family: 'Roboto', sans-serif;
- font-size: 21px;
- font-weight: 400;
- letter-spacing: 0.63px;
-}
+.stop-experiment-button:hover {
+ background-color: #bf181e;
+}
\ No newline at end of file
diff --git a/src/styles/ExperimentCreation.css b/src/styles/ExperimentCreation.css
index 9cb5692..762f732 100644
--- a/src/styles/ExperimentCreation.css
+++ b/src/styles/ExperimentCreation.css
@@ -3,7 +3,7 @@
color: #000;
font-size: 50px;
font-weight: 700;
- margin-top: 90px; /* Adjust for navbar height */
+ margin-top: 100px;
margin-left: 24px;
user-select: none;
}
@@ -58,7 +58,7 @@
font-family: 'Roboto', sans-serif;
font-size: 25px;
font-weight: 300;
- color: rgba(0, 0, 0, 0.55);
+ color: rgb(0, 0, 0);
margin-left: 140px; /* Align with labels */
margin-top: 5px; /* Space below labels */
}
@@ -110,7 +110,7 @@
padding: 0 10px;
font-size: 25px;
font-weight: 300;
- color: rgba(0, 0, 0, 0.55);
+ color: rgb(0, 0, 0);
border: 3px solid rgba(0, 0, 0, 0.25);
border-radius: 12px;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.25);
diff --git a/src/styles/Message.css b/src/styles/Message.css
new file mode 100644
index 0000000..e609bff
--- /dev/null
+++ b/src/styles/Message.css
@@ -0,0 +1,71 @@
+/* Message.css */
+
+/* Reset and Base Styles */
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: 'Roboto', sans-serif;
+}
+
+/* Message Container */
+.message-container {
+ max-width: 1200px; /* Set a max width */
+ margin: 0 auto; /* Center the container */
+ padding: 20px;
+}
+
+/* Title Styling */
+.message-view-title {
+ position: absolute;
+ top: 90px;
+ left: 25px;
+ width: 433px;
+ height: 60px;
+ color: #000;
+ font-family: 'Inter', sans-serif;
+ font-size: 50px;
+ font-weight: 700;
+ line-height: normal;
+}
+
+/* Message Details Component */
+.message-details-component {
+ width: 100%;
+ margin-top: 160px;
+}
+
+/* Message Not Found Styling */
+.message-not-found {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 80vh;
+ font-size: 24px;
+ color: #555;
+}
+
+/* Additional Responsive Styles (Optional) */
+@media (max-width: 768px) {
+ .message-view-title {
+ font-size: 36px;
+ width: auto;
+ left: 10px;
+ }
+
+ .message-details-component {
+ margin-top: 140px;
+ }
+
+ .dynamic-view {
+ padding: 15px;
+ }
+
+ .field-name {
+ display: block;
+ margin-bottom: 5px;
+ }
+}
diff --git a/src/styles/MessageCreation.css b/src/styles/MessageCreation.css
index 2c6426e..fcef8af 100644
--- a/src/styles/MessageCreation.css
+++ b/src/styles/MessageCreation.css
@@ -10,16 +10,14 @@
/* Content Box */
.message-creation-content-box {
- position: absolute;
- top: 170px; /* Position it below the title */
- left: 50%;
- transform: translateX(-50%);
width: 1460px;
- height: auto; /* Adjust height to fit content */
- flex-shrink: 0;
+ margin: 20px auto; /* Center the content box */
border: 1px solid #000;
background: rgba(217, 217, 217, 0.00);
- padding: 20px 0; /* Add padding for inner spacing */
+ padding: 20px;
+ border-radius: 12px; /* Optional: Add rounded corners */
+ max-height: calc(100vh - 200px); /* Adjust based on header and margins */
+ overflow-y: auto; /* Enable vertical scrolling */
}
/* Section Label */
@@ -47,7 +45,7 @@
font-family: 'Roboto', sans-serif;
font-size: 25px;
font-weight: 300;
- color: rgba(0, 0, 0, 0.55);
+ color: rgba(0, 0, 0,);
margin-left: 140px; /* Align with labels */
margin-top: 5px; /* Space below labels */
height: 25px; /* Match the input height */
@@ -56,8 +54,8 @@
/* Flex Container for Sections */
.message-creation-flex-container {
display: flex;
- align-items: flex-start;
- justify-content: space-between;
+ flex-direction: column; /* Stack items vertically */
+ gap: 20px; /* Adjust spacing between sections */
margin-top: 20px;
padding: 0 140px;
}
@@ -147,7 +145,7 @@
/* Dropdown Button */
.message-creation-dropdown-button {
- width: 100%;
+ width: 40%;
height: 50px;
background: #27A966;
color: #fff;
@@ -185,7 +183,7 @@
bottom: 55px;
left: 0;
background-color: #fff;
- min-width: 100%;
+ min-width: 40%;
max-height: 200px;
overflow-y: auto;
box-shadow: 0px -8px 16px rgba(0, 0, 0, 0.2);
@@ -219,7 +217,7 @@
font-family: 'Roboto', sans-serif;
font-size: 25px;
font-weight: 300;
- color: rgba(0, 0, 0, 0.55);
+ color: rgb(0, 0, 0);
margin-top: 5px;
}
@@ -227,7 +225,7 @@
.message-creation-submit-button {
position: fixed;
bottom: 12px;
- right: 6px;
+ right: 20px;
width: 106px;
height: 102px;
background: none;
@@ -237,6 +235,25 @@
z-index: 1000;
}
+/* Custom Scrollbar Styles */
+.message-creation-content-box::-webkit-scrollbar {
+ width: 6px; /* Increased width for better visibility */
+}
+
+.message-creation-content-box::-webkit-scrollbar-track {
+ background: #f1f1f1;
+ border-radius: 10px;
+}
+
+.message-creation-content-box::-webkit-scrollbar-thumb {
+ background: #888;
+ border-radius: 10px;
+}
+
+.message-creation-content-box::-webkit-scrollbar-thumb:hover {
+ background: #555;
+}
+
@media (max-width: 1600px) {
.message-creation-flex-container {
flex-direction: column;
diff --git a/src/styles/MessageDashboard.css b/src/styles/MessageDashboard.css
index bb8b3eb..a855aa5 100644
--- a/src/styles/MessageDashboard.css
+++ b/src/styles/MessageDashboard.css
@@ -2,10 +2,9 @@
.message-dashboard-container {
font-family: 'Roboto', sans-serif;
position: relative;
- overflow-x: hidden;
- min-height: 100%;
display: flex;
flex-direction: column;
+ /* Removed overflow-x and min-height to enable scrollbar */
}
/* Messages Title */
@@ -16,6 +15,7 @@
margin-top: 100px; /* Adjust for navbar height */
margin-left: 24px;
user-select: none;
+ flex-shrink: 0; /* Prevent shrinking */
}
/* Filters Container */
@@ -26,6 +26,7 @@
margin-top: 20px;
margin-left: 5%;
margin-right: 5%;
+ flex-shrink: 0; /* Prevent shrinking */
}
/* Filter Styles */
@@ -44,12 +45,17 @@
user-select: none;
}
-/* InteractionType Filter */
+.interactiontype-filter{
+ margin-right: 20px;
+}
+/* Dropdown Filter */
.dropdown {
position: relative;
width: 225px;
}
-
+.species-filter .dropdown {
+ width: 400px; /* Adjust the value as needed */
+}
.dropdown-button {
width: 100%;
height: 50px;
@@ -124,7 +130,7 @@
padding-right: 40px;
font-size: 25px;
font-weight: 300;
- color: rgba(0, 0, 0, 0);
+ color: rgba(0, 0, 0, 0); /* Hidden text */
border: 3px solid rgba(0, 0, 0, 0.25);
border-radius: 12px;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.25);
@@ -136,6 +142,10 @@
box-shadow: 0 0 5px #27A966;
}
+.search-input input[type="text"]:focus {
+ color: #000; /* Reveal text on focus */
+}
+
.search-icon {
position: absolute;
right: 10px;
@@ -153,10 +163,11 @@
margin-right: 2%;
overflow-x: auto;
overflow-y: auto; /* Enables vertical scrolling */
- max-height: calc(100vh - 180px); /* Set desired maximum height */
+ height: calc(100vh - 270px); /* Adjusted total offset */
user-select: none;
}
+/* Messages Table */
.messages-table {
width: 100%;
border-collapse: collapse;
@@ -164,7 +175,7 @@
.messages-table th,
.messages-table td {
- padding: 10px;
+ padding: 12px;
text-align: center;
}
@@ -232,6 +243,7 @@
border: none;
padding: 0;
cursor: pointer;
+ z-index: 1000; /* Ensure it appears above other elements */
}
.add-message-button img {
@@ -239,6 +251,7 @@
height: auto;
}
+/* Loading Container */
.loading-container {
display: flex;
justify-content: center;
@@ -262,7 +275,7 @@
/* Scrollbar Styling (Optional) */
.message-table-container::-webkit-scrollbar {
- width: 8px;
+ width: 8px;
}
.message-table-container::-webkit-scrollbar-track {
@@ -277,4 +290,44 @@
.message-table-container::-webkit-scrollbar-thumb:hover {
background: #555;
-}
\ No newline at end of file
+}
+
+/* Responsive Adjustments (Optional) */
+@media (max-width: 768px) {
+ .messages-title {
+ font-size: 36px;
+ margin-top: 70px;
+ margin-left: 16px;
+ }
+
+ .filter-label {
+ font-size: 18px;
+ }
+
+ .dropdown-button {
+ height: 45px;
+ font-size: 18px;
+ }
+
+ .search-input input[type="text"] {
+ width: 250px;
+ height: 45px;
+ font-size: 20px;
+ }
+
+ .search-icon {
+ width: 25px;
+ height: 25px;
+ }
+
+ .messages-table th,
+ .messages-table td {
+ padding: 8px;
+ font-size: 14px;
+ }
+
+ .add-message-button {
+ width: 90px;
+ height: 90px;
+ }
+}
diff --git a/src/styles/Questionnaire.css b/src/styles/Questionnaire.css
new file mode 100644
index 0000000..3386dfc
--- /dev/null
+++ b/src/styles/Questionnaire.css
@@ -0,0 +1,62 @@
+/* Questionnaire.css */
+
+/* Container for the Questionnaire View */
+.questionnaire-view-container {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 20px;
+ font-family: 'Roboto', sans-serif;
+}
+
+/* Title Styling */
+.questionnaire-view-title {
+ position: absolute;
+ top: 90px;
+ left: 25px;
+ width: 600px;
+ height: 60px;
+ color: #000;
+ font-family: 'Inter', sans-serif;
+ font-size: 50px;
+ font-weight: 700;
+ line-height: normal;
+}
+
+/* Details Component */
+.questionnaire-view-details {
+ width: 100%;
+ margin-top: 160px;
+}
+
+/* Not Found Styling */
+.questionnaire-view-not-found {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 80vh;
+ font-size: 24px;
+ color: #555;
+ font-family: 'Roboto', sans-serif;
+}
+
+/* Additional Responsive Styles (Optional) */
+@media (max-width: 768px) {
+ .questionnaire-view-title {
+ font-size: 36px;
+ width: auto;
+ left: 10px;
+ }
+
+ .questionnaire-view-details {
+ margin-top: 140px;
+ }
+
+ .questionnaire-view-container .dynamic-view {
+ padding: 15px;
+ }
+
+ .questionnaire-view-container .field-name {
+ display: block;
+ margin-bottom: 5px;
+ }
+}
diff --git a/src/styles/QuestionnaireCreation.css b/src/styles/QuestionnaireCreation.css
new file mode 100644
index 0000000..fe7c964
--- /dev/null
+++ b/src/styles/QuestionnaireCreation.css
@@ -0,0 +1,149 @@
+/* Container */
+.questionnaire-creation-container {
+ overflow: hidden;
+}
+
+/* Page Title */
+.questionnaire-creation-page-title {
+ color: #000;
+ font-size: 50px;
+ font-weight: 700;
+ margin-top: 100px;
+ margin-left: 24px;
+ user-select: none;
+}
+
+/* Content Box */
+.questionnaire-creation-content-box {
+ position: absolute;
+ top: 170px;
+ left: 50%;
+ transform: translateX(-50%);
+ width: 1460px;
+ height: auto;
+ flex-shrink: 0;
+ border: 1px solid #000;
+ background: rgba(217, 217, 217, 0.0);
+ padding: 20px 0;
+}
+
+/* Section Label */
+.questionnaire-creation-section-label {
+ display: block;
+ width: 250px;
+ height: 32px;
+ color: #000;
+ font-family: 'Roboto', sans-serif;
+ font-size: 27px;
+ font-weight: 300;
+ line-height: normal;
+ margin-left: 140px;
+ margin-top: 15px;
+}
+
+/* Text Input */
+.questionnaire-creation-text-input {
+ width: calc(100% - 280px);
+ border-radius: 12px;
+ border: 3px solid rgba(0, 0, 0, 0.25);
+ background: rgba(196, 196, 196, 0.0);
+ box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.25);
+ padding: 22px 41px;
+ font-family: 'Roboto', sans-serif;
+ font-size: 25px;
+ font-weight: 300;
+ color: rgb(0, 0, 0);
+ margin-left: 140px;
+ margin-top: 5px;
+}
+
+/* Dropdown */
+.questionnaire-creation-dropdown {
+ position: relative;
+ width: calc(100% - 280px);
+ margin-left: 140px;
+ margin-top: 5px;
+}
+
+.questionnaire-creation-dropdown-button {
+ width: 30%;
+ height: 50px;
+ background: #27A966;
+ color: #fff;
+ font-size: 20px;
+ font-weight: 500;
+ border: none;
+ border-radius: 12px;
+ box-shadow: 0px 4px 10px rgba(39, 169, 102, 0.25);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ user-select: none;
+ cursor: pointer;
+ padding: 0 15px;
+}
+
+.questionnaire-creation-dropdown-button:focus {
+ outline: none;
+}
+
+.questionnaire-creation-dropdown-icon {
+ position: absolute;
+ right: 15px;
+ width: 17px;
+ height: 11px;
+ transition: transform 0.2s;
+}
+
+.questionnaire-creation-dropdown.open .questionnaire-creation-dropdown-icon {
+ transform: rotate(180deg);
+}
+
+.questionnaire-creation-dropdown-content {
+ display: none;
+ position: absolute;
+ top: 55px;
+ left: 0;
+ background-color: #fff;
+ min-width: 30%;
+ max-height: 200px;
+ overflow-y: auto;
+ box-shadow: 0px 8px 16px rgba(0, 0, 0, 0.2);
+ z-index: 1;
+ border-radius: 12px;
+}
+
+.questionnaire-creation-dropdown.open .questionnaire-creation-dropdown-content {
+ display: block;
+}
+
+.questionnaire-creation-dropdown-item {
+ padding: 10px;
+ font-size: 18px;
+ cursor: pointer;
+}
+
+.questionnaire-creation-dropdown-item:hover {
+ background-color: #f1f1f1;
+}
+
+/* Submit Button */
+.questionnaire-creation-submit-button {
+ position: fixed;
+ bottom: 12px;
+ right: 6px;
+ width: 106px;
+ height: 102px;
+ background: none;
+ border: none;
+ padding: 0;
+ cursor: pointer;
+ z-index: 1000;
+}
+
+@media (max-width: 1600px) {
+ .questionnaire-creation-content-box {
+ width: 90%;
+ }
+}
diff --git a/src/styles/QuestionnaireDashboard.css b/src/styles/QuestionnaireDashboard.css
new file mode 100644
index 0000000..975eb63
--- /dev/null
+++ b/src/styles/QuestionnaireDashboard.css
@@ -0,0 +1,328 @@
+/* Questionnaire Dashboard Container */
+.questionnaire-dashboard-container {
+ font-family: 'Roboto', sans-serif;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+}
+
+/* Questionnaires Title */
+.questionnaires-title {
+ color: #000;
+ font-size: 50px;
+ font-weight: 700;
+ margin-top: 100px;
+ margin-left: 24px;
+ user-select: none;
+ flex-shrink: 0;
+}
+
+/* Filters Container */
+.questionnaire-filters-container {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-top: 20px;
+ margin-left: 5%;
+ margin-right: 5%;
+ flex-shrink: 0;
+}
+
+/* Filter Styles */
+.questionnaire-filter {
+ display: flex;
+ align-items: center;
+ margin-bottom: 20px;
+}
+
+/* Filter Labels */
+.questionnaire-filter-label {
+ color: #000;
+ font-size: 20px;
+ font-weight: 500;
+ margin-right: 10px;
+ user-select: none;
+}
+
+/* Dropdown */
+.dropdown {
+ position: relative;
+ width: 225px;
+}
+
+.dropdown-button {
+ width: 100%;
+ height: 50px;
+ background: #27A966;
+ color: #fff;
+ font-size: 20px;
+ font-weight: 500;
+ border: none;
+ border-radius: 12px;
+ box-shadow: 0px 4px 10px rgba(233, 68, 75, 0.25);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ user-select: none;
+ cursor: pointer;
+}
+
+.dropdown-icon {
+ position: absolute;
+ right: 15px;
+ width: 17px;
+ height: 11px;
+ transition: transform 0.2s;
+ transform: rotate(180deg);
+}
+
+.dropdown-icon-hover {
+ transform: rotate(0deg);
+}
+
+.dropdown-content {
+ display: none;
+ position: absolute;
+ background-color: #fff;
+ min-width: 100%;
+ max-height: 200px;
+ overflow-y: auto;
+ box-shadow: 0px 8px 16px rgba(0,0,0,0.2);
+ z-index: 1;
+ border-radius: 12px;
+ margin-top: 5px;
+}
+
+.dropdown.open .dropdown-content {
+ display: block;
+}
+
+.dropdown-item {
+ padding: 10px;
+ font-size: 18px;
+ cursor: pointer;
+}
+
+.dropdown-item:hover {
+ background-color: #f1f1f1;
+}
+
+/* Search Filter */
+.search-filter {
+ position: relative;
+}
+
+.search-input {
+ position: relative;
+}
+
+.search-input input[type="text"] {
+ width: 350px;
+ height: 50px;
+ padding: 0 10px;
+ padding-right: 40px;
+ font-size: 25px;
+ font-weight: 300;
+ color: rgba(0, 0, 0, 0);
+ border: 3px solid rgba(0, 0, 0, 0.25);
+ border-radius: 12px;
+ box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.25);
+ transition: border-color 0.2s, box-shadow 0.2s;
+}
+
+.search-input input[type="text"]:hover:not(:focus) {
+ border-color: #27A966;
+ box-shadow: 0 0 5px #27A966;
+}
+
+.search-input input[type="text"]:focus {
+ color: #000;
+}
+
+.search-icon {
+ position: absolute;
+ right: 10px;
+ top: 10px;
+ width: 30px;
+ height: 30px;
+ user-select: none;
+ pointer-events: none;
+}
+
+/* Table Container */
+.questionnaire-table-container {
+ margin-top: 20px;
+ margin-left: 2%;
+ margin-right: 2%;
+ overflow-x: auto;
+ overflow-y: auto;
+ height: calc(100vh - 270px);
+ user-select: none;
+}
+
+/* Questionnaires Table */
+.questionnaires-table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+.questionnaires-table th,
+.questionnaires-table td {
+ padding: 12px;
+ text-align: center;
+}
+
+.questionnaires-table th {
+ background: #D9D9D9;
+ border: 1px solid #000;
+ font-size: 20px;
+ font-weight: 500;
+ letter-spacing: 0.6px;
+ position: sticky;
+ top: 0;
+ cursor: pointer;
+}
+
+.questionnaires-table td {
+ font-size: 15px;
+ font-weight: 400;
+ letter-spacing: 0.45px;
+ cursor: pointer;
+}
+
+.questionnaires-table th,
+.questionnaires-table td {
+ border-bottom: 1px solid rgba(0, 0, 0, 0.30);
+}
+
+.sort-icon {
+ margin-left: 5px;
+ width: 10px;
+ height: 10px;
+ transition: transform 0.2s;
+ pointer-events: none;
+}
+
+.sort-ascending {
+ transform: rotate(180deg);
+}
+
+.sort-descending {
+ transform: rotate(0deg);
+}
+
+/* Hover Effect for Questionnaire Rows */
+.questionnaires-table tbody tr:hover {
+ background-color: rgba(217, 217, 217, 0.8);
+ box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
+}
+
+/* Row Styles */
+.row-even {
+ background-color: rgba(217, 217, 217, 0.60);
+}
+
+.row-odd {
+ background-color: rgba(217, 217, 217, 0.40);
+}
+
+/* Add Questionnaire Button */
+.add-questionnaire-button {
+ position: fixed;
+ bottom: 12px;
+ right: 6px;
+ width: 106px;
+ height: 102px;
+ background: none;
+ border: none;
+ padding: 0;
+ cursor: pointer;
+ z-index: 1000;
+}
+
+.add-questionnaire-button img {
+ width: 100%;
+ height: auto;
+}
+
+/* Loading Container */
+.loading-container {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 300px;
+}
+
+.spinner {
+ border: 16px solid #f3f3f3;
+ border-top: 16px solid #27A966;
+ border-radius: 50%;
+ width: 120px;
+ height: 120px;
+ animation: spin 2s linear infinite;
+}
+
+@keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+}
+
+/* Scrollbar Styling */
+.questionnaire-table-container::-webkit-scrollbar {
+ width: 8px;
+}
+
+.questionnaire-table-container::-webkit-scrollbar-track {
+ background: #f1f1f1;
+ border-radius: 4px;
+}
+
+.questionnaire-table-container::-webkit-scrollbar-thumb {
+ background: #888;
+ border-radius: 4px;
+}
+
+.questionnaire-table-container::-webkit-scrollbar-thumb:hover {
+ background: #555;
+}
+
+/* Responsive Adjustments */
+@media (max-width: 768px) {
+ .questionnaires-title {
+ font-size: 36px;
+ margin-top: 70px;
+ margin-left: 16px;
+ }
+
+ .filter-label {
+ font-size: 18px;
+ }
+
+ .dropdown-button {
+ height: 45px;
+ font-size: 18px;
+ }
+
+ .search-input input[type="text"] {
+ width: 250px;
+ height: 45px;
+ font-size: 20px;
+ }
+
+ .search-icon {
+ width: 25px;
+ height: 25px;
+ }
+
+ .questionnaires-table th,
+ .questionnaires-table td {
+ padding: 8px;
+ font-size: 14px;
+ }
+
+ .add-questionnaire-button {
+ width: 90px;
+ height: 90px;
+ }
+}
diff --git a/src/types/answer.ts b/src/types/answer.ts
new file mode 100644
index 0000000..cf6bb5f
--- /dev/null
+++ b/src/types/answer.ts
@@ -0,0 +1,6 @@
+export interface Answer{
+ index: number;
+ nextQuestionID: string;
+ questionId: string;
+ text: string;
+}
\ No newline at end of file
diff --git a/src/types/experiment.ts b/src/types/experiment.ts
index d436234..9bab5aa 100644
--- a/src/types/experiment.ts
+++ b/src/types/experiment.ts
@@ -2,7 +2,6 @@ import { LivingLab } from './livinglab';
import { User } from './user';
export interface Experiment {
- $schema: string;
ID: string;
name: string;
description: string;
diff --git a/src/types/interactiontype.ts b/src/types/interactiontype.ts
new file mode 100644
index 0000000..fe1a132
--- /dev/null
+++ b/src/types/interactiontype.ts
@@ -0,0 +1,5 @@
+export interface InteractionType {
+ ID: string;
+ description: string;
+ name: string;
+}
\ No newline at end of file
diff --git a/src/types/message.ts b/src/types/message.ts
index 9ab99dc..cd074ab 100644
--- a/src/types/message.ts
+++ b/src/types/message.ts
@@ -1,12 +1,17 @@
+import { Experiment } from "./experiment";
+import { Answer } from "./answer";
+import { Species } from "./species";
+
export interface Message{
- $schema: string;
- answerID: string;
+ ID: string;
+ answer: Answer;
encounterMeters: number;
encounterMinutes: number;
- experimentID: string;
+ experiment: Experiment;
name: string;
severity: number;
- speciesID: number;
+ species: Species;
text: string;
trigger: string;
+ activity: number;
}
\ No newline at end of file
diff --git a/src/types/question.ts b/src/types/question.ts
new file mode 100644
index 0000000..e36459a
--- /dev/null
+++ b/src/types/question.ts
@@ -0,0 +1,13 @@
+import { Answer } from './answer';
+
+export interface Question {
+ ID: string;
+ allowMultipleResponse: boolean;
+ allowOpenResponse: boolean;
+ options: string[];
+ answers: Array;
+ description: string;
+ index: number;
+ openResponseFormat: string;
+ text: string;
+}
\ No newline at end of file
diff --git a/src/types/questionnaire.ts b/src/types/questionnaire.ts
new file mode 100644
index 0000000..a9a049b
--- /dev/null
+++ b/src/types/questionnaire.ts
@@ -0,0 +1,19 @@
+import { Experiment } from "./experiment";
+import { InteractionType } from "./interactiontype";
+import { Question } from "./question";
+
+export interface Questionnaire{
+ ID: string;
+ experiment: Experiment;
+ identifier: string;
+ interactionType: InteractionType
+ name: string;
+ questions: Array;
+}
+
+export interface AddQuestionnaire{
+ experimentID: string;
+ identifier: string;
+ interactionTypeID: string;
+ name: string;
+}
\ No newline at end of file
diff --git a/src/types/species.ts b/src/types/species.ts
new file mode 100644
index 0000000..50201cd
--- /dev/null
+++ b/src/types/species.ts
@@ -0,0 +1,5 @@
+export interface Species {
+ ID: string;
+ name: string;
+ commonName: string;
+}
\ No newline at end of file