-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Edit and delete for [expenses|payments]
#76
base: main
Are you sure you want to change the base?
Changes from all commits
4511ff3
3127309
5fa0ad4
acd0bd5
a6faf22
fe050af
4f64b3e
9d1030c
1c53fe7
00f1182
e03df70
ddac870
2bd3589
cbe1042
c029f49
45f83cc
eef0063
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,15 @@ | ||
'use client' | ||
|
||
import ErrorSummary from "@/app/components/ErrorSummary" | ||
import RequiredFieldDescription from "@/app/components/RequiredFieldDescription" | ||
import TextFieldWithValidation from "@/app/components/TextFieldWithValidation" | ||
import { PaymentItem } from "@/lib/features/job/payment/paymentSlice" | ||
import { Button, DatePicker, Form, FormGroup, Label, RequiredMarker } from "@trussworks/react-uswds" | ||
import { Controller, SubmitHandler, useForm } from "react-hook-form" | ||
import { useTranslation } from "react-i18next" | ||
|
||
import { PaymentItem } from "@/lib/features/job/payment/paymentSlice" | ||
|
||
import ErrorSummary from "@/app/components/ErrorSummary" | ||
import RequiredFieldDescription from "@/app/components/RequiredFieldDescription" | ||
import TextFieldWithValidation from "@/app/components/TextFieldWithValidation" | ||
|
||
export interface FormPaymentProps { | ||
onSubmit: SubmitHandler<FormPaymentData> | ||
item?: PaymentItem | ||
|
@@ -23,12 +25,34 @@ export type FormPaymentData = { | |
export default function FormPayment(params: FormPaymentProps) { | ||
const { t } = useTranslation() | ||
|
||
let formatDate = () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. there is a world where the date methods i wrote for the tests could be generalized as utils. however, it's important to have some separation of the code under test and the code used to test. i've left these separate for that reason, but this could be a change down the line. the deciding question there will be "by combining date utils, am i still testing what i think i'm testing?" truss, maintainer of React USWDS disagrees with me |
||
let formattedDate: string = ''; | ||
if (params.item?.date) { | ||
const paymentDate = new Date(params.item?.date) | ||
|
||
const addLeadingZero = (d: number) => d.toString().length === 1 ? | ||
`0${d}` : d | ||
|
||
formattedDate = `${paymentDate.getFullYear()}-${addLeadingZero(paymentDate.getMonth())}-${addLeadingZero(paymentDate.getDate())}` | ||
} | ||
|
||
return formattedDate | ||
} | ||
const formattedDate = formatDate() | ||
|
||
const { | ||
register, | ||
getValues, | ||
control, | ||
formState: { errors }, | ||
handleSubmit | ||
} = useForm<FormPaymentData>() | ||
} = useForm<FormPaymentData>({ | ||
defaultValues: { | ||
amount: params.item?.amount, | ||
date: formattedDate, | ||
payer: params.item?.payer | ||
} | ||
}) | ||
|
||
return ( | ||
<Form onSubmit={handleSubmit(params.onSubmit)}> | ||
|
@@ -52,12 +76,16 @@ export default function FormPayment(params: FormPaymentProps) { | |
name="date" | ||
control={control} | ||
rules={{ required: {value:true, message: t('add_income_required_field')} }} | ||
render={({ field }) => ( | ||
render={({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is a buncha churn on getting the datepicker and form validation working. i left them in as a demonstration at how it could work. they don't affect functionality (or, what didn't work before still doesn't work now lolsob) |
||
field: { onChange, name }, | ||
}) => ( | ||
<> | ||
<Label htmlFor="date" className="text-bold">{t('add_income_payment_date')}<RequiredMarker /></Label> | ||
<DatePicker | ||
id="date" | ||
{...field} | ||
name={name} | ||
defaultValue={formattedDate} | ||
onChange={onChange} | ||
{...(errors.date?.message !== undefined ? {validationStatus: 'error'} : {})} | ||
/> | ||
</> | ||
|
@@ -78,8 +106,12 @@ export default function FormPayment(params: FormPaymentProps) { | |
/> | ||
</FormGroup> | ||
<FormGroup> | ||
<Button type="submit" name="continue_button" data-testid="continue_button">{t('add_income_button_done')}</Button> | ||
<Button type="submit" name="continue_button" data-testid="continue_button" onClick={() => { | ||
console.log(getValues()) | ||
}}>{t('add_income_button_done')}</Button> | ||
</FormGroup> | ||
|
||
{/* <DevTool control={control} /> */} | ||
</Form> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,12 +8,12 @@ import { Button, Form, FormGroup, Checkbox, DatePicker, ComboBox, Label, Require | |
import { Controller, SubmitHandler, useForm } from "react-hook-form" | ||
import { useTranslation } from "react-i18next" | ||
|
||
interface ExpenseFormPaymentProps { | ||
onSubmit: SubmitHandler<ExpenseFormPaymentData> | ||
interface FormExpenseProps { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. naming consistency change |
||
onSubmit: SubmitHandler<FormExpenseData> | ||
item?: ExpenseItem | ||
} | ||
|
||
export type ExpenseFormPaymentData = { | ||
export type FormExpenseData = { | ||
job: string | ||
name: string | ||
expenseType: string | ||
|
@@ -22,7 +22,7 @@ export type ExpenseFormPaymentData = { | |
isMileage: boolean | ||
} | ||
|
||
export default function FormExpense(params: ExpenseFormPaymentProps) { | ||
export default function FormExpense(params: FormExpenseProps) { | ||
const { t } = useTranslation() | ||
|
||
const expenseTypeOptions = [ | ||
|
@@ -41,7 +41,7 @@ export default function FormExpense(params: ExpenseFormPaymentProps) { | |
control, | ||
formState: { errors }, | ||
handleSubmit | ||
} = useForm<ExpenseFormPaymentData>() | ||
} = useForm<FormExpenseData>() | ||
|
||
return (<Form onSubmit={handleSubmit(params.onSubmit)}> | ||
<RequiredFieldDescription /> | ||
|
@@ -54,6 +54,7 @@ export default function FormExpense(params: ExpenseFormPaymentProps) { | |
label={t('add_expense_name_field')} | ||
error={errors.name?.message} | ||
data-testid="name" | ||
value={params.item?.name?.toString()} | ||
requiredMarker | ||
/> | ||
</FormGroup> | ||
|
@@ -104,6 +105,7 @@ export default function FormExpense(params: ExpenseFormPaymentProps) { | |
error={errors.amount?.message} | ||
data-testid="amount" | ||
requiredMarker | ||
value={params.item?.amount?.toString()} | ||
/> | ||
</FormGroup> | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import { beforeAll, describe, expect, it, vi } from 'vitest' | ||
import { fireEvent, render, screen, waitFor } from '@testing-library/react' | ||
import TestWrapper from '@/app/TestWrapper' | ||
import mockRouter from 'next-router-mock' | ||
|
||
import { generateExpense, generateJob } from '@/test/fixtures/generator' | ||
import { generateFormattedDate, today } from '@/test/fixtures/date' | ||
|
||
import Page from './page' | ||
import { EnhancedStore } from '@reduxjs/toolkit/react' | ||
import { makeStore } from '@/lib/store' | ||
|
||
import { addJob } from '@/lib/features/job/jobSlice' | ||
import { addExpense } from '@/lib/features/job/expenses/expensesSlice' | ||
|
||
describe('Edit expenses', () => { | ||
let store: EnhancedStore | ||
const item1 = generateJob() | ||
const expense1 = generateExpense(item1.id) | ||
|
||
beforeAll(() => { | ||
vi.mock('next/navigation', () => ({ | ||
useRouter: () => mockRouter, | ||
usePathname: () => mockRouter.asPath, | ||
})) | ||
|
||
mockRouter.push('/job/0/payment/0/edit') | ||
store = makeStore() | ||
store.dispatch(addJob(item1)) | ||
store.dispatch(addExpense(expense1)) | ||
render (<TestWrapper store={store}><Page params={{jobId: item1.id, expenseId: expense1.id}} /></TestWrapper>) | ||
}) | ||
|
||
it('should load all existing items ot the page', () => { | ||
expect(screen.getByTestId('amount')).toHaveProperty('value', expense1.item.amount.toString()) | ||
// expect(screen.getByTestId('date-picker-external-input')).toHaveProperty('value', expense1.item.date) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. all items that use the form validation |
||
expect(screen.getByTestId('name')).toHaveProperty('value', expense1.item.name) | ||
// expect(screen.getByTestId('expenseType')).toHaveProperty('value', expense1.item.expenseType) | ||
// expect(screen.getByTestId('isMileage')).toHaveProperty('value', expense1.item.isMileage) | ||
|
||
}) | ||
|
||
it('should allow for editing of items on the page', async ()=> { | ||
const expected = { | ||
amount: '11', | ||
date: '11', | ||
fullDate: '', | ||
name: 'My client' | ||
} | ||
expected.fullDate = generateFormattedDate(today(), expected.date) | ||
|
||
fireEvent.click(screen.getByTestId('amount'), { | ||
target: { | ||
value: expected.amount | ||
} | ||
}) | ||
fireEvent.click(screen.getByTestId('date-picker-button')) | ||
// const dateButton = screen.getByText(expected.date) | ||
// fireEvent.click(dateButton) | ||
fireEvent.change(screen.getByTestId("name"), { | ||
target: { | ||
value: expected.name | ||
} | ||
}) | ||
|
||
await waitFor(() => { | ||
expect(screen.getByTestId('amount')).toHaveProperty('value', expected.amount) | ||
// expect(screen.getByTestId('date-picker-external-input')).toHaveProperty('value', expected.fullDate) | ||
expect(screen.getByTestId('name')).toHaveProperty('value', expected.name) | ||
}) | ||
}) | ||
it.skip('when there are existing entries, unchanged fields should not trigger form validation') | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
'use client' | ||
|
||
import { useDispatch } from "react-redux" | ||
import { useRouter } from "next/navigation" | ||
import { Grid, GridContainer } from "@trussworks/react-uswds" | ||
import { useTranslation } from "react-i18next" | ||
import { useAppSelector } from "@/lib/hooks" | ||
|
||
import { SetExpensePayload, selectExpenseItemAt, setExpenseItem } from "@/lib/features/job/expenses/expensesSlice" | ||
import { selectJobItemAt } from "@/lib/features/job/jobSlice" | ||
|
||
import FormExpense, { FormExpenseData } from '@/app/[locale]/job/[jobId]/expense/FormExpense' | ||
import VerifyNav from "@/app/components/VerifyNav" | ||
|
||
|
||
export default function EditExpense({ params }: { params: { expenseId: string, jobId: string } }) { | ||
const { t } = useTranslation() | ||
const dispatch = useDispatch() | ||
const router = useRouter() | ||
const expense = useAppSelector(state => selectExpenseItemAt(state, params.expenseId)) | ||
|
||
const job = useAppSelector(state => selectJobItemAt(state, params.jobId)) | ||
const jobDescription = job ? job.description : 'your job' | ||
|
||
function editExpenseClicked({job=params.jobId, name, expenseType, amount, isMileage=false, date}: FormExpenseData) { | ||
const id = params.expenseId | ||
const expense: SetExpensePayload = { | ||
id, | ||
item: { | ||
job, | ||
name, | ||
expenseType, | ||
amount, | ||
isMileage, | ||
date, | ||
} | ||
} | ||
|
||
dispatch(setExpenseItem(expense)) | ||
|
||
router.push(`/expense/list`) | ||
} | ||
|
||
return ( | ||
<div> | ||
<VerifyNav title={t('edit_income_title')} /> | ||
<div className="usa-section"> | ||
<GridContainer> | ||
<Grid row gap> | ||
<main className="usa-layout-docs"> | ||
<h3>{t('add_income_payment_header', {description: jobDescription })}</h3> | ||
<FormExpense onSubmit={editExpenseClicked} item={expense} /> | ||
</main> | ||
</Grid> | ||
</GridContainer> | ||
</div> | ||
</div> | ||
) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is reordering of
import
s to be more standard (react->get data->render pages)