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);
|
||||
|
||||
@@ -8,6 +8,8 @@ type Props = {
|
||||
content?: Array<{ value: string, icon: React.ReactElement }>;
|
||||
className?: string;
|
||||
name?: string;
|
||||
onValueChange?: (value: number[]) => void;
|
||||
defaultValue?: number[];
|
||||
}
|
||||
|
||||
const defaultContent: Array<{ value: string, icon: React.ReactElement }> = [
|
||||
@@ -18,7 +20,7 @@ const defaultContent: Array<{ value: string, icon: React.ReactElement }> = [
|
||||
{ value: '1', icon: <Star className='fill-black dark:fill-foreground me-1 mb-1' size={24} /> },
|
||||
]
|
||||
|
||||
function RateSelectionBar({ name, content = defaultContent, className = '' }: Props) {
|
||||
function RateSelectionBar({ name, content = defaultContent, className = '', onValueChange, defaultValue = [5] }: Props) {
|
||||
return (
|
||||
<div className={`${className}`}>
|
||||
<div className='relative w-full h-3 rounded-full'>
|
||||
@@ -26,7 +28,8 @@ function RateSelectionBar({ name, content = defaultContent, className = '' }: Pr
|
||||
min={1}
|
||||
max={content.length}
|
||||
name={name}
|
||||
defaultValue={[5]}
|
||||
defaultValue={defaultValue}
|
||||
onValueChange={onValueChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-between items-center mt-2">
|
||||
|
||||
+35
-31
@@ -1,33 +1,37 @@
|
||||
{
|
||||
"Heading": "امتیاز شما به رستوران",
|
||||
"Tabs": {
|
||||
"Strengths": {
|
||||
"Title": "نقاط قوت",
|
||||
"Options": [
|
||||
"آماده سازی سریع",
|
||||
"بهداشت محیطی خوب",
|
||||
"کیفیت خوب منو",
|
||||
"تنوع بالا منو",
|
||||
"برخورد مناسب کارکنان",
|
||||
"قیمت مناسب"
|
||||
]
|
||||
},
|
||||
"Weeknesses": {
|
||||
"Title": "نقاط ضعف",
|
||||
"Options": [
|
||||
"آماده سازی زمانبر",
|
||||
"بهداشت محیطی بد",
|
||||
"کیفیت خوب پایین",
|
||||
"تنوع کم منو",
|
||||
"برخورد غیر حرفه ای کارکنان",
|
||||
"قیمت نامناسب"
|
||||
]
|
||||
}
|
||||
"Heading": "امتیاز شما به رستوران",
|
||||
"Tabs": {
|
||||
"Strengths": {
|
||||
"Title": "نقاط قوت",
|
||||
"Options": {
|
||||
"great_taste": "طعم عالی",
|
||||
"fast_delivery": "تحویل سریع",
|
||||
"good_quality": "کیفیت خوب",
|
||||
"good_portion_size": "اندازه مناسب پرس",
|
||||
"friendly_service": "خدمات دوستانه"
|
||||
}
|
||||
},
|
||||
"InputComment": {
|
||||
"Label": "نظر شما",
|
||||
"Placeholder": "نظر شما"
|
||||
},
|
||||
"ButtonSubmit": "ثبت بازخورد",
|
||||
"ButtonCancel": "بازگشت"
|
||||
}
|
||||
"Weeknesses": {
|
||||
"Title": "نقاط ضعف",
|
||||
"Options": {
|
||||
"small_portion": "پرس کوچک",
|
||||
"slow_delivery": "تحویل کند",
|
||||
"poor_quality": "کیفیت پایین",
|
||||
"overpriced": "قیمت بالا",
|
||||
"unfriendly_service": "خدمات نامناسب"
|
||||
}
|
||||
}
|
||||
},
|
||||
"InputComment": {
|
||||
"Label": "نظر شما",
|
||||
"Placeholder": "نظر شما"
|
||||
},
|
||||
"ButtonSubmit": "ثبت بازخورد",
|
||||
"ButtonCancel": "بازگشت",
|
||||
"Errors": {
|
||||
"RatingRequired": "لطفا امتیاز خود را انتخاب کنید",
|
||||
"SubmitFailed": "خطا در ثبت نظر"
|
||||
},
|
||||
"Success": "نظر شما با موفقیت ثبت شد",
|
||||
"ButtonSubmitting": "در حال ارسال..."
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user