Source: react/flashcardCom.js

import React, { useState, useEffect } from 'react';
import {
  List, ListItem, ListItemText, Button, Divider, Dialog, DialogTitle,
  DialogContent, DialogContentText, DialogActions, TextField, InputAdornment, IconButton, Paper, Close
} from '@mui/material';
import SearchIcon from '@mui/icons-material/Search';
import FlashcardRepo from '../repositories/FlashcardRepo';
import { useParams, useNavigate } from 'react-router-dom';
import ClearIcon from '@mui/icons-material/Clear';
import Quiz from './Quiz';



const astyle = {
  fontFamily: `-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'`,
  colors: {
    primary: '#007aff',
    secondary: '#f2f2f7'
  },
  borderRadius: '10px'
};
/**
 * @class FlashcardComponent
 * @classdesc FlashcardComponent
 * FlashcardComponent - A React component for managing flashcards.
 * This component allows users to view, add, edit, and delete flashcards and their subjects.
 * It also provides navigation to flashcard details and associated quizzes.
 */

const FlashcardComponent = () => {
  const [subjects, setSubjects] = useState([]);
  const [selectedSubject, setSelectedSubject] = useState(null);
  const [flashcards, setFlashcards] = useState({});
  const [open, setOpen] = useState(false);
  const [newEntry, setNewEntry] = useState('');
  const [dialogType, setDialogType] = useState('subject');
  const [editedName, setEditedName] = useState('');
  const [currentlyEditingTopic, setCurrentlyEditingTopic] = useState(null);
  const [showQuiz, setShowQuiz] = useState(false);
  const { setId, quizId } = useParams();
  const [searchQuery, setSearchQuery] = useState('');


  /**
   * @memberof FlashcardComponent
   * @function handleSearchChange
   * @description Handles changes to the search field.
   * @public
   * @param {Object} event - The event object from the search input field.
   */
  const handleSearchChange = (event) => {
    setSearchQuery(event.target.value.toLowerCase());
  };
  /**
   * @memberof FlashcardComponent
   * @function handleClearSearch
   * @description Clears the current search query.
   */
  const handleClearSearch = () => {
    setSearchQuery('');
  };
  /**
   * @memberof FlashcardComponent
   * @function filteredFlashcards
   * @description Filters flashcards based on the search query.
   * @public
   * @param {string} subject - The subject to filter flashcards under.
   * @returns {Array} An array of filtered flashcards.
   */
  const filteredFlashcards = (subject) => {
    return flashcards[subject] ? flashcards[subject].filter(topic => topic.toLowerCase().includes(searchQuery)) : [];
  };
  // Navigation hook from react-router-dom
  const navigate = useNavigate();

  /**
   * @memberof FlashcardComponent
   * @function handleFlashcardClick
   * @description Handles the action when a flashcard is clicked.
   * @description Navigates to the flashcard details page.
   * @public
   * @param {string} topicName - The name of the topic.
   */
  const handleFlashcardClick = async (topicName) => {
    try {
      const setId = await FlashcardRepo.getSetIdByTopicName(topicName);
      if (setId) {
        navigate(`/flashcard-ui/${setId}`);
      } else {
        console.error("Unable to fetch set ID for topic:", topicName);
      }
    } catch (error) {
      console.error("Error in handleFlashcardClick:", error);
    }
  };

  /**
   * @memberof FlashcardComponent
   * @function handleQuizClick
   * @description Handles the action when the quiz button is clicked.
   * @description Navigates to the associated quiz page.
   * @public
   * @param {string} topicName - The name of the topic.
   */
  const handleQuizClick = async (topicName) => {
    try {
      const setId = await FlashcardRepo.getSetIdByTopicName(topicName);
      const quizId = await FlashcardRepo.getQuizIdByTopicName(topicName);
      if (setId) {
        console.log("SetId: ", setId);
        console.log("QuizId: ", quizId);
        navigate(`/quizFlash/${setId}/quiz/${quizId}`);
      } else {
        console.error("Unable to fetch set ID for topic:", topicName);
      }
    } catch (error) {
      console.error("Error in handleFlashcardClick:", error);
    }
  };


  useEffect(() => {
    async function fetchData() {
      try {
        const uid = FlashcardRepo.getCurrentUid();
        console.log("User ID:", uid);

        if (uid) {
          const userSubjects = await FlashcardRepo.getUserSubjects(uid);
          console.log("Fetched user subjects:", userSubjects);

          setSubjects(userSubjects);

          const ownedFlashcardSetIds = await FlashcardRepo.getUserFlashcardSets(uid);
          console.log("Fetched flashcard set IDs:", ownedFlashcardSetIds);

          const ownedFlashcards = [];
          for (let setId of ownedFlashcardSetIds) {
            const flashcardSet = await FlashcardRepo.getFlashcardSetById(setId);
            console.log("Fetched flashcard set:", flashcardSet);
            ownedFlashcards.push(flashcardSet);
          }

          const flashcardData = {};
          for (let flashcardSet of ownedFlashcards) {
            const { name, subject } = flashcardSet;
            if (!flashcardData[subject]) {
              flashcardData[subject] = [];
            }
            flashcardData[subject].push(name);
          }
          console.log("Constructed flashcard data:", flashcardData);
          setFlashcards(flashcardData);
        }
      } catch (error) {
        console.error("Error fetching user data:", error);
      }
    }
    fetchData();
  }, []);
  /**
   * @memberof FlashcardComponent
   * @function handleAddTopic
     * @description Handles adding a new topic.
     */
  const handleAddTopic = async () => {
    try {
      const uid = FlashcardRepo.getCurrentUid();
      if (uid) {
        // retreived both flashcardSetID and quizSetID
        const { flashcardSetId, quizSetId } = await FlashcardRepo.createFlashcardSet({
          name: newEntry,
          subject: selectedSubject
        });
        console.log('selectedSubject:', selectedSubject);

        await FlashcardRepo.addOwnedFlashcardSetToUser(uid, flashcardSetId);
        // add user created quiz to the user collection
        await FlashcardRepo.addOwnedQuizSetToUser(uid, quizSetId);

        // Update the local state
        const currentFlashcards = flashcards[selectedSubject] || [];
        setFlashcards(prev => ({
          ...prev,
          [selectedSubject]: [...currentFlashcards, newEntry]
        }));
      }
    } catch (error) {
      console.error("Error adding new topic:", error);
    }
  };
  /**
   * @memberof FlashcardComponent
   * @function handleDelete
     * @description Handles the deletion of a topic.
     * @public
     * @param {string} topicName - The name of the topic to delete.
     */
  const handleDelete = async (topicName) => {
    try {
      const uid = FlashcardRepo.getCurrentUid();


      const setId = await FlashcardRepo.getSetIdByTopicName(topicName);


      await FlashcardRepo.removeSetIdFromUser(uid, setId);


      await FlashcardRepo.removeUidFromSharedWith(setId, uid);

      const updatedFlashcards = flashcards[selectedSubject].filter(t => t !== topicName);
      setFlashcards(prev => ({
        ...prev,
        [selectedSubject]: updatedFlashcards
      }));
    } catch (error) {
      console.error("Error deleting flashcard set:", error);
    }
  };
  /**
   * @memberof FlashcardComponent
   * @function handleAdd
     * @description Handles the action when the add button is clicked.
     */
  const handleAdd = async () => {
    const trimmedEntry = newEntry.trim();

    if (trimmedEntry) {
      if (dialogType === 'subject') {
        if (!subjects.includes(trimmedEntry)) {
          // Update the local state
          setSubjects(prev => [...prev, trimmedEntry]);
          setFlashcards(prev => ({ ...prev, [trimmedEntry]: [] }));

          // Update the Firebase database
          const uid = FlashcardRepo.getCurrentUid();
          if (uid) {
            try {
              await FlashcardRepo.addUserSubject(uid, trimmedEntry);
            } catch (error) {
              console.error("Error adding subject to Firebase:", error);
            }
          }
        }

      } else if (dialogType === 'topic' && selectedSubject) {

        handleAddTopic();
      }
    }
    setNewEntry('');
    setOpen(false);
  };
  /**
   * @memberof FlashcardComponent
   * @function handleEdit
     * @description Handles editing an existing topic.
     */
  const handleEdit = async () => {
    console.log('handleEdit called');

    if (editedName.trim() === "" || !currentlyEditingTopic) {
      console.log('Empty edited name or no topic being edited, exiting');
      return;
    }

    try {

      const setId = await FlashcardRepo.getSetIdByTopicName(currentlyEditingTopic);
      console.log('Set ID by topic name:', setId);

      // Update the Firebase database
      await FlashcardRepo.updateFlashcardSetName(setId, editedName.trim());

      // Update the local state
      const updatedFlashcards = flashcards[selectedSubject].map(t => t === currentlyEditingTopic ? editedName.trim() : t);
      console.log('Updated flashcards:', updatedFlashcards);

      setFlashcards(prev => ({
        ...prev,
        [selectedSubject]: updatedFlashcards
      }));

      setCurrentlyEditingTopic(null);
      setEditedName('');


    } catch (error) {
      console.error("Error editing flashcard set name:", error);
    }
  }

  return (
    <div style={{ display: 'flex', marginTop: '60px', fontFamily: astyle.fontFamily }}>
      {/* Sidebar */}
      <Paper elevation={3} style={{ width: '240px', overflow: 'auto' }}>
        <Divider />
        <List>
          {subjects.map((subject) => (
            <ListItem
              button
              key={subject}
              onClick={() => setSelectedSubject(subject)}
              style={{ padding: '15px 10px' }}
            >
              <ListItemText primary={subject} />
            </ListItem>
          ))}
        </List>
        <Button
          variant="contained"
          color="primary"
          style={{
            margin: 'auto 10px 10px 10px',
            backgroundColor: astyle.colors.primary,
            borderRadius: astyle.borderRadius
          }}
          onClick={() => {
            setOpen(true);
            setDialogType('subject');
          }}
        >
          Add Subject
        </Button>
      </Paper>

      {/* right Flashcard Sets */}
      <div style={{ flexGrow: 1, padding: '20px', marginLeft: '260px', backgroundColor: astyle.colors.secondary }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
          <h2>{selectedSubject}</h2>
          <Button variant="contained" color="primary" onClick={() => { setOpen(true); setDialogType('topic'); }}>
            Add Topic
          </Button>
        </div>

        <TextField
          variant="outlined"
          size="small"
          fullWidth
          placeholder="Search..."
          value={searchQuery}
          onChange={handleSearchChange}
          InputProps={{
            startAdornment: (
              <InputAdornment position="start">
                <IconButton>
                  <SearchIcon />
                </IconButton>
              </InputAdornment>
            ),
            endAdornment: searchQuery && (
              <InputAdornment position="end">
                <IconButton onClick={handleClearSearch}>
                  <ClearIcon />
                </IconButton>
              </InputAdornment>
            )
          }}
        />

        {flashcards[selectedSubject]?.map((topic, index) => (
          <div key={index} style={{ display: 'flex', flexDirection: 'column', marginBottom: '20px' }}>
            {
              currentlyEditingTopic === topic ? (
                <div style={{ display: 'flex', gap: '10px', alignItems: 'center' }}>
                  <TextField
                    variant="outlined"
                    size="small"
                    value={editedName}
                    onChange={(e) => setEditedName(e.target.value)}
                  />
                  <Button variant="contained" color="primary" onClick={handleEdit}>Save</Button>
                </div>
              ) : (
                <h3>{topic}</h3>
              )
            }
            <div style={{ display: 'flex', gap: '10px' }}>
              <Button variant="outlined" onClick={() => {

                if (currentlyEditingTopic) {

                  setCurrentlyEditingTopic(null);
                  setEditedName('');
                } else {

                  setCurrentlyEditingTopic(topic);
                  setEditedName(topic);
                }
              }}>Edit</Button>
              <Button variant="outlined" onClick={() => handleFlashcardClick(topic)}>Flashcard</Button>
              <Button variant="outlined" onClick={() => handleQuizClick(topic)}>Quiz</Button>
              <Button variant="outlined" color="secondary" onClick={() => handleDelete(topic)}>Delete</Button>
            </div>
          </div>
        ))}
      </div>

      {/* Shared Add Dialog */}
      <Dialog open={open} onClose={() => setOpen(false)}>
        <DialogTitle>{`Add a New ${dialogType.charAt(0).toUpperCase() + dialogType.slice(1)}`}</DialogTitle>
        <DialogContent>
          <DialogContentText>
            {`Please enter the name of the new ${dialogType} you'd like to add:`}
          </DialogContentText>
          <TextField
            autoFocus
            margin="dense"
            label={`${dialogType.charAt(0).toUpperCase() + dialogType.slice(1)} Name`}
            type="text"
            fullWidth
            value={newEntry}
            onChange={(e) => setNewEntry(e.target.value)}
          />
        </DialogContent>
        <DialogActions>
          <Button onClick={() => setOpen(false)} color="primary">
            Cancel
          </Button>
          <Button onClick={handleAdd} color="primary">
            Add
          </Button>
        </DialogActions>
      </Dialog>
    </div >
  );


};

export default FlashcardComponent;