diff --git a/api/handlers/dispute/model.go b/api/handlers/dispute/model.go index 24966e79..59c05c91 100644 --- a/api/handlers/dispute/model.go +++ b/api/handlers/dispute/model.go @@ -489,7 +489,7 @@ func (m *disputeModelReal) GetDisputeExperts(disputeId int64) (experts []models. Select("users.id, users.first_name || ' ' || users.surname AS full_name, email, users.phone_number AS phone, role"). Joins("JOIN users ON dispute_experts_view.expert = users.id"). Where("dispute = ?", disputeId). - Where("dispute_experts_view.status = 'Approved'"). + Where("dispute_experts_view.status IN ('Approved', 'Review')"). Where("role = 'Mediator' OR role = 'Arbitrator' OR role = 'Conciliator' OR role = 'expert'"). Find(&experts).Error diff --git a/api/handlers/ticket/model.go b/api/handlers/ticket/model.go index d8677a79..73a94e21 100644 --- a/api/handlers/ticket/model.go +++ b/api/handlers/ticket/model.go @@ -56,6 +56,11 @@ func (t *TicketModelReal) CreateTicket(userID int64, dispute int64, subject stri err := t.db.Where("id = ?", dispute). Where("complainant = ? OR respondant = ?", userID, userID). First(&disputeModel).Error + if err != nil { + logger.Warn("User might be an expert") + err = t.db.Where("id = ?", dispute). + Where("expert = ?", userID).First(&disputeModel).Error + } if err != nil { logger.WithError(err).Error("Error creating ticket") return models.Ticket{}, err @@ -265,20 +270,16 @@ func (t *TicketModelReal) GetTicketsByUserID(uid int64, searchTerm *string, limi queryParams = append(queryParams, uid) countParams = append(countParams, uid) if searchTerm != nil { - queryString.WriteString(" AND WHERE t.subject LIKE ?") - countString.WriteString(" AND WHERE t.subject LIKE ?") + queryString.WriteString(" AND t.subject LIKE ?") + countString.WriteString(" AND t.subject LIKE ?") queryParams = append(queryParams, "%"+*searchTerm+"%") countParams = append(countParams, "%"+*searchTerm+"%") } if filters != nil && len(*filters) > 0 { - if searchTerm != nil { - queryString.WriteString(" AND ") - countString.WriteString(" AND ") - } else { - queryString.WriteString(" AND WHERE ") - countString.WriteString(" AND WHERE ") - } + queryString.WriteString(" AND ") + countString.WriteString(" AND ") + for i, filter := range *filters { queryString.WriteString("t." + filter.Attr + " = ?") countString.WriteString("t." + filter.Attr + " = ?") diff --git a/api/models/requestModels.go b/api/models/requestModels.go index a2da5ed2..9d2a2b13 100644 --- a/api/models/requestModels.go +++ b/api/models/requestModels.go @@ -170,7 +170,7 @@ type Filter struct { Attr string `json:"attr"` // The value to search for - Value string `json:"value"` + Value interface{} `json:"value"` } type DateFilter struct { diff --git a/api/models/utilityModels.go b/api/models/utilityModels.go index 4d8910db..f62267b5 100644 --- a/api/models/utilityModels.go +++ b/api/models/utilityModels.go @@ -109,6 +109,12 @@ type AdminDisputeExperts struct { Status string `gorm:"column:status" json:"status"` } +type DisputeExpertsView struct { + Dispute int64 `gorm:"column:dispute" json:"dispute"` + Expert int64 `gorm:"column:expert" json:"expert"` + Status ExpertStatus `gorm:"column:status" json:"status"` +} + // type TicketMessage struct { // ID // } diff --git a/frontend/src/app/disputes/[id]/page.tsx b/frontend/src/app/disputes/[id]/page.tsx index 79a8a1ab..7f31e435 100644 --- a/frontend/src/app/disputes/[id]/page.tsx +++ b/frontend/src/app/disputes/[id]/page.tsx @@ -1,7 +1,7 @@ import { Badge } from "@/components/ui/badge"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Separator } from "@/components/ui/separator"; -import { getDisputeDetails } from "@/lib/api/dispute"; +import { getDisputeDetails, getDisputeWorkflow } from "@/lib/api/dispute"; import { Metadata } from "next"; import DisputeClientPage from "./client-page"; @@ -14,6 +14,9 @@ import DisputeDecisionForm from "@/components/dispute/decision-form"; import CreateTicketDialog from "@/components/dispute/ticket-form"; import { Button } from "@/components/ui/button"; import WorkflowSelect from "@/components/form/workflow-select"; +import DisputeHeader from "@/components/dispute/dispute-header"; +import Link from "next/link"; +import { State } from "@/lib/interfaces/workflow"; type Props = { params: { id: string }; @@ -28,17 +31,19 @@ export async function generateMetadata({ params }: Props): Promise { export default async function DisputePage({ params }: Props) { const { data, error } = await getDisputeDetails(params.id); + const workflow = await getDisputeWorkflow(params.id); if (error || !data) { return

{error}

; } return (
- @@ -49,49 +54,30 @@ export default async function DisputePage({ params }: Props) { ); } -function DisputeHeader({ - id, - label, - startDate, - status: initialStatus, -}: { +function DisputeHeader2(props: { id: string; label: string; startDate: string; status: string; + state: State; }) { // TODO: Add contracts for this const user = (jwtDecode(getAuthToken()) as any).user.id; const role = (jwtDecode(getAuthToken()) as any).user.role; return ( -
-
-

{label}

-

Started: {startDate}

- {/*TODO: Figure out the conditions for displaying expert rejection */} - {role == "expert" && } + + {role == "expert" && } - {role == "expert" && ( - - - - )} + {role == "expert" && ( + + + + )} - - - - -
- -
-
Dispute ID:
-
{id}
-
Status:
-
- -
-
-
+ +
); } diff --git a/frontend/src/app/disputes/[id]/tickets/[tid]/message-form.tsx b/frontend/src/app/disputes/[id]/tickets/[tid]/message-form.tsx new file mode 100644 index 00000000..fb45f5e4 --- /dev/null +++ b/frontend/src/app/disputes/[id]/tickets/[tid]/message-form.tsx @@ -0,0 +1,49 @@ +"use client"; + +import { Button } from "@/components/ui/button"; +import { Card, CardHeader, CardTitle, CardContent, CardFooter } from "@/components/ui/card"; +import { Textarea } from "@/components/ui/textarea"; +import { addTicketMessage } from "@/lib/api/tickets"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; + +const messageSchema = z.object({ + message: z.string().trim().min(1), +}); +type MessageData = z.infer; + +export default function MessageForm({ ticket }: { ticket: number }) { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + resolver: zodResolver(messageSchema), + }); + + async function onSubmit(data: MessageData) { + await addTicketMessage(ticket, data.message); + } + + return ( + +
+ + Send a message + + +