keanacobarde / on-paper

Spend your money OnPaper first!
0 stars 0 forks source link

MVP - CREATE/EDIT - MONTHLY INCOME FORM #15

Closed keanacobarde closed 11 months ago

keanacobarde commented 1 year ago

User Story

I, as the broke student or job juggling over-achiever, wish to have a full timeline of my expenses on paper. I would like to track, monthly, all of my expenses, which categories they fall under, and what that month's total income was. Each month will have its own dedicated monthly summary so that trends can be spotted over time. I want to be able to report my monthly income or edit the current month I'm on. If any changes need to be made, I should have the flexibility to make those changes.

Acceptance Criteria

WIREFRAME

image

ERD

image

WHEN, the user is either on a month they've already reported the income for, or if you're on a new month without a monthly income, you can either select the 'Edit Month' button under the month name or the 'Add next month...' button below the reported monthly income. If you're on a new month without a reported income, the 'Edit Month' button shouldn't appear and there should only be and 'Add this month...' button.

WHEN either the 'Edit Month' or 'Add this month...' is selected, a modal should appear. Depending on where the user is, a month should automatically be taken into consideration along with a year, and a form will appear that allows you to add your monthly income.

Dependencies

ALL TESTING DATA SHOULD BE CREATED. ALL NECESSARY API CALLS SHOULD BE WRITTEN. A SEPERATE ISSUE TICKET WILL BE CREATED FOR THIS ONCE FULL FUNCTIONALITY IS MAPPED AND DECIDED (CREATE/EDIT ON MONTHLY INCOME TABLE)

14 - This is a mutual dependency. The timeline page is not entirely complete without the functionality of this feature; however, this feature cannot be tested without, at least, a skeleton of this page created.

Dev Notes

EXAMPLE FROM SIMPLY BOOKS

BOOKS > NEW.JS

import React from 'react';
import BookForm from '../../components/forms/BookForm';

// TODO: create a reusable form to add/edit book and render in this view

export default function AddBook() {
  return <BookForm />;
}

BOOKFORM.JS

import React, { useEffect, useState } from 'react';
import { useRouter } from 'next/router';
import PropTypes from 'prop-types';
import FloatingLabel from 'react-bootstrap/FloatingLabel';
import Form from 'react-bootstrap/Form';
import { Button } from 'react-bootstrap';
import { useAuth } from '../../utils/context/authContext';
import { getAuthors } from '../../api/authorData';
import { createBook, updateBook } from '../../api/bookData';

const initialState = {
  description: '',
  image: '',
  price: '',
  sale: false,
  title: '',
};

function BookForm({ obj }) {
  const [formInput, setFormInput] = useState(initialState);
  const [authors, setAuthors] = useState([]);
  const router = useRouter();
  const { user } = useAuth();

  useEffect(() => {
    getAuthors(user.uid).then(setAuthors);

    if (obj.firebaseKey) setFormInput(obj);
  }, [obj, user]);

  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormInput((prevState) => ({
      ...prevState,
      [name]: value,
    }));
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    if (obj.firebaseKey) {
      updateBook(formInput).then(() => router.push(`/book/${obj.firebaseKey}`));
    } else {
      const payload = { ...formInput, uid: user.uid };
      createBook(payload).then(({ name }) => {
        const patchPayload = { firebaseKey: name };
        updateBook(patchPayload).then(() => {
          router.push('/');
        });
      });
    }
  };
  return (
    <Form onSubmit={handleSubmit}>
      <h2 className="text-white mt-5">{obj.firebaseKey ? 'Update' : 'Create'} Book</h2>

      {/* TITLE INPUT  */}
      <FloatingLabel controlId="floatingInput1" label="Book Title" className="mb-3">
        <Form.Control
          type="text"
          placeholder="Enter a title"
          name="title"
          value={formInput.title}
          onChange={handleChange}
          required
        />
      </FloatingLabel>

      {/* IMAGE INPUT  */}
      <FloatingLabel controlId="floatingInput2" label="Book Image" className="mb-3">
        <Form.Control
          type="url"
          placeholder="Enter an image url"
          name="image"
          value={formInput.image}
          onChange={handleChange}
          required
        />
      </FloatingLabel>

      {/* PRICE INPUT  */}
      <FloatingLabel controlId="floatingInput3" label="Book Price" className="mb-3">
        <Form.Control
          type="text"
          placeholder="Enter price"
          name="price"
          value={formInput.price}
          onChange={handleChange}
          required
        />
      </FloatingLabel>

      {/* AUTHOR SELECT  */}
      <FloatingLabel controlId="floatingSelect" label="Author">
        <Form.Select
          aria-label="Author"
          name="author_id"
          onChange={handleChange}
          className="mb-3"
          value={obj.author_id} // FIXME: modify code to remove error
          required
        >
          <option value="">Select an Author</option>
          {
            authors.map((author) => (
              <option
                key={author.firebaseKey}
                value={author.firebaseKey}
              >
                {author.first_name} {author.last_name}
              </option>
            ))
          }
        </Form.Select>
      </FloatingLabel>

      {/* DESCRIPTION TEXTAREA  */}
      <FloatingLabel controlId="floatingTextarea" label="Description" className="mb-3">
        <Form.Control
          as="textarea"
          placeholder="Description"
          style={{ height: '100px' }}
          name="description"
          value={formInput.description}
          onChange={handleChange}
          required
        />
      </FloatingLabel>

      {/* A WAY TO HANDLE UPDATES FOR TOGGLES, RADIOS, ETC  */}
      <Form.Check
        className="text-white mb-3"
        type="switch"
        id="sale"
        name="sale"
        label="On Sale?"
        checked={formInput.sale}
        onChange={(e) => {
          setFormInput((prevState) => ({
            ...prevState,
            sale: e.target.checked,
          }));
        }}
      />

      {/* SUBMIT BUTTON  */}
      <Button type="submit">{obj.firebaseKey ? 'Update' : 'Create'} Book</Button>
    </Form>
  );
}

BookForm.propTypes = {
  obj: PropTypes.shape({
    description: PropTypes.string,
    image: PropTypes.string,
    price: PropTypes.string,
    sale: PropTypes.bool,
    title: PropTypes.string,
    author_id: PropTypes.string,
    firebaseKey: PropTypes.string,
  }),
};

BookForm.defaultProps = {
  obj: initialState,
};

export default BookForm;

'EDIT' - SEEN WITHIN BOOKS > EDIT > [FIREBASEKEY].JS, DEPENDENT ON FORM COMPONENT FOR COMPLETION

import { React, useEffect, useState } from 'react';
import { useRouter } from 'next/router';
import { getSingleBook } from '../../../api/bookData';
import BookForm from '../../../components/forms/BookForm';

export default function EditBook() {
  const [editItem, setEditItem] = useState({});
  const router = useRouter();
  // TODO: grab the firebasekey
  const { firebaseKey } = router.query;

  // TODO: make a call to the API to get the book data
  useEffect(() => {
    getSingleBook(firebaseKey).then(setEditItem);
  }, [firebaseKey]);

  // TODO: pass object to form
  return (<BookForm obj={editItem} />);
}
keanacobarde commented 11 months ago

TESTED CREATE AND EDIT OF MONTHS SUCCESSFULLY. CLOSING TICKET.