submit review
This commit is contained in:
@@ -6,9 +6,14 @@ import { TabHeader } from '@/components/tab/TabHeader';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import RateSelectionBar from '@/components/utils/RateSelectionBar';
|
||||
import clsx from 'clsx';
|
||||
import { useParams } from 'next/navigation';
|
||||
import React from 'react'
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useCreateRate } from '../hooks/useRateData';
|
||||
import { CreateRateType } from '../types/Types';
|
||||
import { toast } from '@/components/Toast';
|
||||
import { PositivePointEnum, NegativePointEnum } from '../enum/Enum';
|
||||
import { extractErrorMessage } from '@/lib/func';
|
||||
|
||||
type Props = object
|
||||
|
||||
@@ -17,24 +22,61 @@ type Params = {
|
||||
}
|
||||
|
||||
function RatingOrderIndex({ }: Props) {
|
||||
const params: Params = useParams();
|
||||
console.log(params);
|
||||
|
||||
const { t } = useTranslation("rating");
|
||||
const params: Params = useParams();
|
||||
const router = useRouter();
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const { mutate: createRate, isPending } = useCreateRate();
|
||||
|
||||
const [rating, setRating] = useState<number>(5);
|
||||
const [comment, setComment] = useState<string>('');
|
||||
const [positivePoints, setPositivePoints] = useState<string[]>([]);
|
||||
const [negativePoints, setNegativePoints] = useState<string[]>([]);
|
||||
|
||||
const positivePointsArray = [
|
||||
PositivePointEnum.GREAT_TASTE,
|
||||
PositivePointEnum.FAST_DELIVERY,
|
||||
PositivePointEnum.GOOD_QUALITY,
|
||||
PositivePointEnum.GOOD_PORTION_SIZE,
|
||||
PositivePointEnum.FRIENDLY_SERVICE,
|
||||
];
|
||||
|
||||
const negativePointsArray = [
|
||||
NegativePointEnum.SMALL_PORTION,
|
||||
NegativePointEnum.SLOW_DELIVERY,
|
||||
NegativePointEnum.POOR_QUALITY,
|
||||
NegativePointEnum.OVERPRICED,
|
||||
NegativePointEnum.UNFRIENDLY_SERVICE,
|
||||
];
|
||||
|
||||
const handleToggle = (pointValue: string, isPositive: boolean) => (checked: boolean) => {
|
||||
const setter = isPositive ? setPositivePoints : setNegativePoints;
|
||||
|
||||
if (checked) {
|
||||
setter(prev => [...prev, pointValue]);
|
||||
} else {
|
||||
setter(prev => prev.filter(p => p !== pointValue));
|
||||
}
|
||||
};
|
||||
|
||||
const badPoitns = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="grid grid-cols-2 gap-x-4.5 gap-y-4 mt-8">
|
||||
{[...Array(6)].map((_, i) => (
|
||||
{negativePointsArray.map((point) => (
|
||||
<ToggleButton
|
||||
className={{
|
||||
className={{
|
||||
...ToggleButtonClassNames,
|
||||
wrapperActive: 'border-red-400',
|
||||
contentActive: 'text-red-400'
|
||||
}}
|
||||
key={i}
|
||||
name={`weekness${i}`}>
|
||||
{t(`Tabs.Weeknesses.Options.${i}`)}
|
||||
key={point}
|
||||
name={`weekness_${point}`}
|
||||
// @ts-expect-error - Type mismatch between custom ToggleButton and React types
|
||||
onToggle={handleToggle(point, false)}
|
||||
>
|
||||
{t(`Tabs.Weeknesses.Options.${point}`)}
|
||||
</ToggleButton>
|
||||
))}
|
||||
</div>
|
||||
@@ -46,9 +88,14 @@ function RatingOrderIndex({ }: Props) {
|
||||
return (
|
||||
<>
|
||||
<div className="grid grid-cols-2 gap-x-4.5 gap-y-4 mt-8">
|
||||
{[...Array(6)].map((_, i) => (
|
||||
<ToggleButton key={i} name={`strength${i}`}>
|
||||
{t(`Tabs.Strengths.Options.${i}`)}
|
||||
{positivePointsArray.map((point) => (
|
||||
<ToggleButton
|
||||
key={point}
|
||||
name={`strength_${point}`}
|
||||
// @ts-expect-error - Type mismatch between custom ToggleButton and React types
|
||||
onToggle={handleToggle(point, true)}
|
||||
>
|
||||
{t(`Tabs.Strengths.Options.${point}`)}
|
||||
</ToggleButton>
|
||||
))}
|
||||
</div>
|
||||
@@ -56,11 +103,41 @@ function RatingOrderIndex({ }: Props) {
|
||||
)
|
||||
}
|
||||
|
||||
const handleRatingChange = (value: number[]) => {
|
||||
setRating(value[0]);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
router.back();
|
||||
};
|
||||
|
||||
const submitForm = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.currentTarget);
|
||||
console.log(formData.get('rating'))
|
||||
}
|
||||
|
||||
if (rating === 0) {
|
||||
toast(t("Errors.RatingRequired") || "لطفا امتیاز خود را انتخاب کنید", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
const rateData: CreateRateType = {
|
||||
foodId: urlParams.get('foodId') || '',
|
||||
orderId: params.orderId,
|
||||
rating,
|
||||
comment,
|
||||
positivePoints,
|
||||
negativePoints,
|
||||
};
|
||||
|
||||
createRate(rateData, {
|
||||
onSuccess: () => {
|
||||
toast(t("Success") || "نظر شما با موفقیت ثبت شد", "success");
|
||||
router.back();
|
||||
},
|
||||
onError: (error) => {
|
||||
toast(extractErrorMessage(error), "error");
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<section className='flex flex-col h-full pt-6'>
|
||||
@@ -70,7 +147,11 @@ function RatingOrderIndex({ }: Props) {
|
||||
|
||||
<div>
|
||||
<div className='w-full'>
|
||||
<RateSelectionBar name='rating' />
|
||||
<RateSelectionBar
|
||||
name='rating'
|
||||
defaultValue={[rating]}
|
||||
onValueChange={handleRatingChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-10'>
|
||||
@@ -81,33 +162,48 @@ function RatingOrderIndex({ }: Props) {
|
||||
...TabContainerClassNames,
|
||||
wrapper: 'p-2!',
|
||||
scrollView: clsx(
|
||||
'border-none! rounded-xl! gap-0! h-full bg-[#EAECF0]! dark:bg-neutral-900! not-dark:gradient-border! grid! grid-cols-2 px-2! py-2! !pb-2 w-full overflow-y-hidden justify-center items-center',
|
||||
'border-none! rounded-xl! gap-0! h-full bg-[#EAECF0]! dark:bg-neutral-900! not-dark:gradient-border! grid! grid-cols-2 px-2! py-2! pb-2! w-full overflow-y-hidden justify-center items-center',
|
||||
),
|
||||
title: 'text-sm2! font-normal mt-1',
|
||||
header: 'rounded-lg h-7! w-full',
|
||||
headerActive: 'bg-primary',
|
||||
titleActive: 'text-accent'
|
||||
headerActive: 'bg-white',
|
||||
titleActive: 'text-black'
|
||||
}}>
|
||||
<TabHeader title={t("Tabs.Weeknesses.Title")} viewRenderer={badPoitns()}></TabHeader>
|
||||
<TabHeader title={t("Tabs.Strengths.Title")} viewRenderer={goodPoitns()}></TabHeader>
|
||||
</TabContainer>
|
||||
<div className="w-full mt-6">
|
||||
<label htmlFor='userOpinion'>{t("InputComment.Label")}</label>
|
||||
<label htmlFor='userOpinion' className='text-sm font-medium'>
|
||||
{t("InputComment.Label")}
|
||||
</label>
|
||||
<br />
|
||||
<textarea name='userOpinion' id='userOpinion' placeholder={t("InputComment.Placeholder")} className='dark:border-neutral-600 mt-2 w-full h-21 px-4 py-2.5 text-xs text-[#888888] rounded-xl border border-[#D0D0D0]' />
|
||||
<textarea
|
||||
name='userOpinion'
|
||||
id='userOpinion'
|
||||
value={comment}
|
||||
onChange={(e) => setComment(e.target.value)}
|
||||
placeholder={t("InputComment.Placeholder")}
|
||||
className='dark:border-neutral-600 dark:bg-neutral-800 mt-2 w-full h-21 px-4 py-2.5 text-xs text-foreground rounded-xl border border-[#D0D0D0] focus:outline-none focus:ring-2 focus:ring-accent'
|
||||
/>
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4 mt-6">
|
||||
<Button type='submit' className='mt-auto rounded-xl font-normal cursor-pointer'>
|
||||
{t("ButtonSubmit")}
|
||||
</Button>
|
||||
<Button
|
||||
type='submit'
|
||||
disabled={isPending}
|
||||
className='mt-auto rounded-xl font-normal cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed'
|
||||
>
|
||||
{isPending ? t("ButtonSubmitting") || "در حال ارسال..." : t("ButtonSubmit")}
|
||||
</Button>
|
||||
<Button
|
||||
type='button'
|
||||
onClick={handleCancel}
|
||||
disabled={isPending}
|
||||
className='
|
||||
mt-auto rounded-xl bg-white dark:bg-container cursor-pointer hover:bg-neutral-100 dark:hover:bg-neutral-700
|
||||
border border-foreground dark:border-neutral-600 text-foreground font-normal'
|
||||
border border-foreground dark:border-neutral-600 text-foreground font-normal disabled:opacity-50 disabled:cursor-not-allowed'
|
||||
>
|
||||
{t("ButtonCancel")}
|
||||
</Button>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
export const enum PositivePointEnum {
|
||||
GREAT_TASTE = "great_taste",
|
||||
FAST_DELIVERY = "fast_delivery",
|
||||
GOOD_QUALITY = "good_quality",
|
||||
GOOD_PORTION_SIZE = "good_portion_size",
|
||||
FRIENDLY_SERVICE = "friendly_service",
|
||||
}
|
||||
|
||||
export const enum NegativePointEnum {
|
||||
SMALL_PORTION = "small_portion",
|
||||
SLOW_DELIVERY = "slow_delivery",
|
||||
POOR_QUALITY = "poor_quality",
|
||||
OVERPRICED = "overpriced",
|
||||
UNFRIENDLY_SERVICE = "unfriendly_service",
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import * as api from "../service/RateService";
|
||||
|
||||
export const useCreateRate = () => {
|
||||
return useMutation({
|
||||
mutationFn: api.createRate,
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
import { api } from "@/config/axios";
|
||||
import { CreateRateType } from "../types/Types";
|
||||
|
||||
export const createRate = async (params: CreateRateType) => {
|
||||
const { data } = await api.post("/public/reviews", params);
|
||||
return data;
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
export type CreateRateType = {
|
||||
foodId: string;
|
||||
orderId: string;
|
||||
rating: number;
|
||||
comment: string;
|
||||
positivePoints: string[];
|
||||
negativePoints: string[];
|
||||
};
|
||||
+14
-10
@@ -262,8 +262,10 @@ html[data-theme="light"] {
|
||||
/* #F4F5F9 */
|
||||
--container: oklch(1 0 0);
|
||||
/* #FFFFFF */
|
||||
--primary: oklch(0.14 0 0);
|
||||
/* #000000 */
|
||||
--primary: oklch(0.202 0 0);
|
||||
/* #333333 - same as foreground for consistency */
|
||||
--primary-foreground: oklch(1 0 0);
|
||||
/* #FFFFFF */
|
||||
--border: oklch(0.885 0.007 248.3);
|
||||
/* #E5E5E5 */
|
||||
--invalid: oklch(0.515 0.118 27.5);
|
||||
@@ -322,27 +324,29 @@ html[data-theme="dark"] {
|
||||
--sidebar-ring: oklch(0.556 0 0); */
|
||||
|
||||
--background: oklch(23.26% 0.014 253.1);
|
||||
/* #F4F5F9 */
|
||||
/* #2B2E3F */
|
||||
--container: oklch(30.33% 0.016 252.42);
|
||||
/* #FFFFFF */
|
||||
--primary: oklch(14% 0.005 285.823);
|
||||
/* #000000 */
|
||||
/* #3A3E52 */
|
||||
--primary: oklch(97.807% 0.029 256.847);
|
||||
/* same as foreground for consistency */
|
||||
--primary-foreground: oklch(23.26% 0.014 253.1);
|
||||
/* same as background */
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
/* #E5E5E5 */
|
||||
/* #E5E5E5 with 10% opacity */
|
||||
--invalid: oklch(0.515 0.118 27.5);
|
||||
/* red (#FF0000 approx) */
|
||||
--valid: oklch(0.46 0.107 132.7);
|
||||
/* #439C46 */
|
||||
--foreground: oklch(97.807% 0.029 256.847);
|
||||
/* #333333 */
|
||||
/* #F5F6FA */
|
||||
--disabled: oklch(42.866% 0.00494 286.051);
|
||||
/* #E7E7E7 */
|
||||
/* #5A5D6F */
|
||||
--disabled2: oklch(0.5 0 0);
|
||||
/* #7F7F7F */
|
||||
--disabled3: oklch(0.949 0.002 277.1);
|
||||
/* #F2F2F2 */
|
||||
--disabled-text: oklch(75.73% 0.01398 251.72);
|
||||
/* #8C90A3 */
|
||||
/* #B8BBCC */
|
||||
--menu-header: oklch(0.79 0.03 237.3);
|
||||
/* #C3C7DD */
|
||||
--icon-deactive: oklch(0.65 0.027 234.8);
|
||||
|
||||
Reference in New Issue
Block a user