Skip to content
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

Note edit and delete #507

Merged
merged 5 commits into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion eap_backend/eap_api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,17 @@
views.property_claim_list,
name="property_claim_list",
),
# path(
# "comments/<int:assurance_case_id>/",
# views.comment_list,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove commented code

# name="comment_list",
# ),
path(
"comments/<int:assurance_case_id>/",
"assurance_case/<int:assurance_case_id>/comments/",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although I might disagree with the initial API design, I see it is important to keep in consistent with all the other resources: case_element_in_plural/ for listing/creation deletion where you can send the assurance_case_id via request parameters instead of via slug. Please adjust accordingly.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cptanalatriste do you mean changing assurance_case to assurance_cases ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking doing something similar to goals, another top-level case item attached to assurance case (see goal_list and goal_detail). In that style, we would have two resources: comments/ to handle case-level retrieve and creation and comments/<int:pk> for update, deletion and single-comment retrieval. Just to keep the same design over the whole API

views.comment_list,
name="comment_list",
),
path("comments/<int:pk>/", views.comment_detail, name="comment_detail"),
path(
"comments/<int:comment_id>/reply/",
views.reply_to_comment,
Expand Down
35 changes: 34 additions & 1 deletion eap_backend/eap_api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -641,12 +641,45 @@ def comment_list(request, assurance_case_id):
return Response(serializer.data)

elif request.method == "POST":
serializer = CommentSerializer(data=request.data)
data = request.data.copy()
data["assurance_case_id"] = (
assurance_case_id # Ensure assurance_case_id is set in the data
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, this shouldn't be needed. Aren't you already sending the assurance case ID in the payload body?

)
serializer = CommentSerializer(data=data)
if serializer.is_valid():
# Ensure the author is set to the current user
serializer.save(author=request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

return JsonResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


@api_view(["GET", "PUT", "DELETE"])
def comment_detail(request, pk):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that we're out of "crunch-mode", please at least at some unit tests for the new backend code. At least at the test_view level

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cptanalatriste this is where I was hoping you'd be able to help

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RichGriff of course! I can add a couple of tests to the branch

"""
Retrieve, update or delete a specific comment.
"""
try:
comment = Comment.objects.get(id=pk)
except Comment.DoesNotExist:
return HttpResponse(status=404)

if request.method == "GET":
serializer = CommentSerializer(comment)
return Response(serializer.data)

elif request.method == "PUT":
serializer = CommentSerializer(comment, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

elif request.method == "DELETE":
comment.delete()
return HttpResponse(status=204)

return JsonResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


Expand Down
4 changes: 2 additions & 2 deletions next_frontend/.env.local
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# TODO(cgavidia): This should be handled via environment variables.
# NEXT_PUBLIC_API_URL=http://localhost:8000
NEXT_PUBLIC_API_URL=http://localhost:8000
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do not merge this code into develop

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cptanalatriste good catch! Will switch this out, forgot that we had this in the repo.

# NEXT_PUBLIC_API_URL=https://eap-backend.azurewebsites.net
NEXT_PUBLIC_API_URL=https://staging-eap-backend.azurewebsites.net
# NEXT_PUBLIC_API_URL=https://staging-eap-backend.azurewebsites.net

NEXT_PUBLIC_STORAGESASTOKEN=sv=2022-11-02&ss=bfqt&srt=c&sp=rwdlacupiytfx&se=2025-05-06T03:07:05Z&st=2024-05-05T19:07:05Z&spr=https&sig=vUQsHpRHQ%2BZAiE9k3riG4qmXJubVd0jMdbBFgM0fpuY%3D
NEXT_PUBLIC_STORAGESOURCENAME=teamedia
116 changes: 116 additions & 0 deletions next_frontend/components/cases/NotesEditForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
'use client'

import React, { Dispatch, SetStateAction, useState } from 'react'
import { boolean, z } from "zod"
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import { Button } from "@/components/ui/button"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import { Textarea } from '../ui/textarea'
import { useLoginToken } from '@/hooks/useAuth'
import useStore from '@/data/store'

type NotesEditFormProps = {
note: any,
setEdit: Dispatch<SetStateAction<boolean | undefined>>
}

const formSchema = z.object({
comment: z.string().min(2).max(50),
})

const NotesEditForm = ({ note, setEdit } : NotesEditFormProps ) => {
const [token] = useLoginToken();
const { assuranceCase, setAssuranceCase } = useStore()
const [loading, setLoading] = useState<boolean>(false)

const { id, content } = note

const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
comment: content,
}
})

async function onSubmit(values: z.infer<typeof formSchema>) {
setLoading(true)

const newComment = {
content: values.comment
}

try {
let url = `${process.env.NEXT_PUBLIC_API_URL}/api/comments/${id}/`

const requestOptions: RequestInit = {
method: "PUT",
headers: {
Authorization: `Token ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify(newComment),
};
const response = await fetch(url, requestOptions);

if(!response.ok) {
console.log('error')
}

const updatedComment = await response.json();

// Find the index of the updated comment in the existing comments array
const updatedComments = assuranceCase.comments.map((comment:any) =>
comment.id === updatedComment.id ? updatedComment : comment
);

const updatedAssuranceCase = {
...assuranceCase,
comments: updatedComments,
};

setAssuranceCase(updatedAssuranceCase);
setEdit(false);
setLoading(false)
} catch (error) {
console.log('Error', error)
setLoading(false)
}
}

return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="comment"
render={({ field }) => (
<FormItem>
{/* <FormLabel>Username</FormLabel> */}
<FormControl>
<Textarea placeholder="Type your message here." {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className='flex justify-end items-center gap-2'>
<Button variant={'ghost'} onClick={() => setEdit(false)}>Cancel</Button>
<Button type="submit" disabled={loading}>
{loading ? 'Saving' : 'Save'}
</Button>
</div>
</form>
</Form>
)
}

export default NotesEditForm
69 changes: 62 additions & 7 deletions next_frontend/components/cases/NotesFeed.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import { Fragment, useEffect } from 'react'
'use client'

import { Fragment, useEffect, useState } from 'react'
import { ChatBubbleLeftEllipsisIcon, TagIcon, UserCircleIcon } from '@heroicons/react/20/solid'
import { User2Icon } from 'lucide-react'
import { Edit2, Pencil, PencilLine, PhoneOffIcon, Save, Trash2, User2Icon, X } from 'lucide-react'
import moment from 'moment'
import useStore from '@/data/store'
import { Button } from '../ui/button'
import { useLoginToken } from '@/hooks/useAuth'
import { boolean } from 'zod'
import NotesEditField from './NotesEditForm'
import NotesEditForm from './NotesEditForm'

// const activity = [
// {
Expand Down Expand Up @@ -45,16 +52,51 @@ import useStore from '@/data/store'
// ]

export default function NotesFeed({ }) {
const { assuranceCase } = useStore()
const { assuranceCase, setAssuranceCase } = useStore()
const [token] = useLoginToken();
const [edit, setEdit] = useState<boolean>()
const [newComment, setNewComment] = useState<string>()

//@ts-ignore
assuranceCase.comments.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));

useEffect(() => {
//@ts-ignore
assuranceCase.comments.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
console.log('Comments', assuranceCase.comments)
},[assuranceCase])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove unnecessary logs


const handleNoteDelete = async (id: number) => {
try {
let url = `${process.env.NEXT_PUBLIC_API_URL}/api/comments/${id}/`

const requestOptions: RequestInit = {
method: "DELETE",
headers: {
Authorization: `Token ${token}`,
"Content-Type": "application/json",
}
};
const response = await fetch(url, requestOptions);

if(!response.ok) {
console.log('error')
}

const updatedComments = assuranceCase.comments.filter((comment:any) => comment.id !== id)
console.log('Updated Comments', updatedComments)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idem

const updatedAssuranceCase = {
...assuranceCase,
comments: updatedComments
}

setAssuranceCase(updatedAssuranceCase)
} catch (error) {
console.log('Error', error)
}
}

return (
<div className="mt-4 py-8 px-4">
<ul role="list" className="-mb-8">
Expand All @@ -63,11 +105,11 @@ export default function NotesFeed({ }) {
)}
{assuranceCase.comments.map((activityItem: any, activityItemIdx: any) => (
<li key={crypto.randomUUID()}>
<div className="relative pb-8">
<div className="relative pb-8 group">
{activityItemIdx !== assuranceCase.comments.length - 1 ? (
<span className="absolute left-5 top-5 -ml-px h-full w-0.5 bg-gray-200 dark:bg-gray-800" aria-hidden="true" />
<span className="absolute left-9 top-5 -ml-px h-full w-0.5 bg-gray-200 dark:bg-gray-800" aria-hidden="true" />
) : null}
<div className="relative flex items-start space-x-3">
<div className="relative flex justify-start items-start space-x-3 p-4 rounded-md group-hover:bg-gray-100/50 dark:group-hover:bg-foreground/10">
<div className="relative mr-4">
{/* <img
className="flex h-10 w-10 items-center justify-center rounded-full bg-gray-400 ring-8 ring-white"
Expand All @@ -92,9 +134,22 @@ export default function NotesFeed({ }) {
<p className="mt-0.5 text-sm text-foreground/70">Created On: {moment(new Date(activityItem.created_at)).format('DD/MM/YYYY')}</p>
</div>
<div className="mt-2 text-sm text-foreground">
<p className="whitespace-normal">{activityItem.content}</p> {/* Apply whitespace-normal to force text wrapping */}
{edit ? (
<NotesEditForm note={activityItem} setEdit={setEdit} />
) : (
<p className="whitespace-normal">{activityItem.content}</p>
)}

</div>
</div>
{!edit && (
<div className='hidden group-hover:flex justify-center items-center gap-2'>
<Button onClick={() => setEdit(!edit)} size={'icon'} className='bg-background hover:bg-background/50 text-foreground'>
<PencilLine className='w-4 h-4'/>
</Button>
<Button onClick={() => handleNoteDelete(activityItem.id)} size={'icon'} variant={'destructive'}><Trash2 className='w-4 h-4'/></Button>
</div>
)}
</div>
</div>
</li>
Expand Down
2 changes: 1 addition & 1 deletion next_frontend/components/cases/NotesForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const NotesForm: React.FC<NotesFormProps> = ({ }) => {

async function onSubmit(values: z.infer<typeof formSchema>) {
try {
let url = `${process.env.NEXT_PUBLIC_API_URL}/api/comments/${assuranceCase.id}/`
let url = `${process.env.NEXT_PUBLIC_API_URL}/api/assurance_case/${assuranceCase.id}/comments/`

const requestOptions: RequestInit = {
method: "POST",
Expand Down
Loading