fix grades
This commit is contained in:
parent
148eaf88c9
commit
2bc0a29a0a
@ -2,18 +2,20 @@ import { CleanlinessIcon } from '../../Icons/CleanlinessIcon';
|
||||
import { FacilityIcon } from '../../Icons/FacilityIcon';
|
||||
import { RestaurantIcon } from '../../Icons/RestaurantIcon';
|
||||
import { ServiceIcon } from '../../Icons/ServiceIcon';
|
||||
import { ComfortIcon } from '../../Icons/ComfortIcon';
|
||||
import { LocationType } from '../../../types/common';
|
||||
|
||||
interface RatingData {
|
||||
score: number;
|
||||
count: number;
|
||||
}
|
||||
|
||||
interface DetailRatings {
|
||||
environment: number;
|
||||
cleanliness: number;
|
||||
price: number;
|
||||
facility: number;
|
||||
}
|
||||
type DetailRatings = [
|
||||
{ label: string; value: number | null },
|
||||
{ label: string; value: number | null },
|
||||
{ label: string; value: number | null },
|
||||
{ label: string; value: number | null },
|
||||
];
|
||||
|
||||
interface RatingsCardProps<T> {
|
||||
data: T;
|
||||
@ -65,16 +67,17 @@ const RatingsCard = <T,>({
|
||||
{criticDetails && (
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{([
|
||||
{ label: 'Taste', value: criticDetails.environment, icon: <RestaurantIcon className="w-7 h-7" strokeWidth={1.1} /> },
|
||||
{ label: 'Cleanliness', value: criticDetails.cleanliness, icon: <CleanlinessIcon className="w-7 h-7" strokeWidth={1.1} /> },
|
||||
{ label: 'Service', value: criticDetails.price, icon: <ServiceIcon className="w-7 h-7" strokeWidth={1.1} /> },
|
||||
{ label: 'Facilities', value: criticDetails.facility, icon: <FacilityIcon className="w-7 h-7" strokeWidth={1.1} /> },
|
||||
] as const).map((item) => (
|
||||
//@ts-ignore
|
||||
{ ...criticDetails[0], icon: data.detail.location_type === LocationType.Culinary ? <RestaurantIcon className="w-7 h-7" strokeWidth={1.1} /> : <ComfortIcon className="w-7 h-7" strokeWidth={1.1} /> },
|
||||
{ ...criticDetails[1], icon: <CleanlinessIcon className="w-7 h-7" strokeWidth={1.1} /> },
|
||||
{ ...criticDetails[2], icon: <ServiceIcon className="w-7 h-7" strokeWidth={1.1} /> },
|
||||
{ ...criticDetails[3], icon: <FacilityIcon className="w-7 h-7" strokeWidth={1.1} /> },
|
||||
]).map((item) => (
|
||||
<div key={item.label} className="border border-gray-600 rounded-xl px-3 py-2 flex items-center gap-3">
|
||||
<div className="text-tertiary flex-shrink-0">{item.icon}</div>
|
||||
<div>
|
||||
<div className="text-tertiary text-xs">{item.label}</div>
|
||||
<div className="text-lg font-bold leading-none my-0.5">{item.value}</div>
|
||||
<div className="text-lg font-bold leading-none my-0.5">{item.value ?? '-'}</div>
|
||||
<div className="text-tertiary text-xs">Based on {formatCount(criticData.count)} reviews</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -99,16 +102,17 @@ const RatingsCard = <T,>({
|
||||
{userDetails && (
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{([
|
||||
{ label: 'Taste', value: userDetails.environment, icon: <RestaurantIcon className="w-7 h-7" strokeWidth={1.1} /> },
|
||||
{ label: 'Cleanliness', value: userDetails.cleanliness, icon: <CleanlinessIcon className="w-7 h-7" strokeWidth={1.1} /> },
|
||||
{ label: 'Service', value: userDetails.price, icon: <ServiceIcon className="w-7 h-7" strokeWidth={1.1} /> },
|
||||
{ label: 'Facilities', value: userDetails.facility, icon: <FacilityIcon className="w-7 h-7" strokeWidth={1.1} /> },
|
||||
] as const).map((item) => (
|
||||
//@ts-ignore
|
||||
{ ...userDetails[0], icon: data.detail.location_type === LocationType.Culinary ? <RestaurantIcon className="w-7 h-7" strokeWidth={1.1} /> : <ComfortIcon className="w-7 h-7" strokeWidth={1.1} /> },
|
||||
{ ...userDetails[1], icon: <CleanlinessIcon className="w-7 h-7" strokeWidth={1.1} /> },
|
||||
{ ...userDetails[2], icon: <ServiceIcon className="w-7 h-7" strokeWidth={1.1} /> },
|
||||
{ ...userDetails[3], icon: <FacilityIcon className="w-7 h-7" strokeWidth={1.1} /> },
|
||||
]).map((item) => (
|
||||
<div key={item.label} className="border border-gray-600 rounded-xl px-3 py-2 flex items-center gap-3">
|
||||
<div className="text-tertiary flex-shrink-0">{item.icon}</div>
|
||||
<div>
|
||||
<div className="text-tertiary text-xs">{item.label}</div>
|
||||
<div className="text-lg font-bold leading-none my-0.5">{item.value}</div>
|
||||
<div className="text-lg font-bold leading-none my-0.5">{item.value ?? '-'}</div>
|
||||
<div className="text-tertiary text-xs">Based on {formatCount(userData.count)} reviews</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
43
src/components/Icons/ComfortIcon/index.tsx
Normal file
43
src/components/Icons/ComfortIcon/index.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
interface ComfortIconProps {
|
||||
width?: number;
|
||||
height?: number;
|
||||
fill?: string;
|
||||
className?: string;
|
||||
bold?: boolean;
|
||||
strokeWidth?: number;
|
||||
}
|
||||
|
||||
export function ComfortIcon({ width = 30, height = 30, fill = 'white', className, bold = false, strokeWidth = 0.6 }: ComfortIconProps) {
|
||||
const stroke = bold ? fill : 'none';
|
||||
const sw = bold ? strokeWidth : 0;
|
||||
return (
|
||||
<svg width={width} height={height} viewBox="0 0 300 213" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_4332_1873)">
|
||||
<mask id="mask0_4332_1873" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="-46" width="300" height="300">
|
||||
<path d="M300 -46H0V254H300V-46Z" fill="white" />
|
||||
</mask>
|
||||
<g mask="url(#mask0_4332_1873)">
|
||||
<mask id="mask1_4332_1873" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="-46" width="300" height="300">
|
||||
<path d="M299.707 253.707V-45.707H0.292969V253.707H299.707Z" fill="white" stroke="white" stroke-width="0.585937" />
|
||||
</mask>
|
||||
<g mask="url(#mask1_4332_1873)">
|
||||
<path d="M40.1281 145.015L27.7941 186.711C26.7992 190.423 22.9484 192.647 19.2353 191.653C15.5228 190.657 13.2992 186.806 14.2941 183.093L23.4365 145.015H40.1281Z" stroke="white" stroke-width="11.7187" stroke-miterlimit="22.926" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M23.4369 109.859H276.564C286.232 109.859 294.141 117.769 294.141 127.436C294.141 137.104 286.232 145.015 276.564 145.015H23.4369C13.7689 145.015 5.85938 137.105 5.85938 127.437C5.85938 117.77 13.7689 109.859 23.4369 109.859Z" stroke="white" stroke-width="11.7187" stroke-miterlimit="22.926" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M259.873 145.015L272.207 186.711C273.202 190.423 277.053 192.647 280.766 191.653C284.478 190.657 286.702 186.806 285.707 183.093L276.565 145.015H259.873Z" stroke="white" stroke-width="11.7187" stroke-miterlimit="22.926" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M34.9277 162.594H265.072" stroke="white" stroke-width="11.7187" stroke-miterlimit="22.926" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M42.1875 57.1238C31.875 57.1238 23.4375 65.5613 23.4375 75.8738V109.859M276.564 109.859V34.8582C276.564 24.5451 268.127 16.1082 257.815 16.1082H168.751C158.439 16.1082 150.001 24.5451 150.001 34.8582V38.3738C150.001 48.6863 141.564 57.1238 131.251 57.1238H82.5967" stroke="white" stroke-width="11.7187" stroke-miterlimit="22.926" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M189.818 109.859C190.697 92.9557 203.611 64.7094 220.456 64.8922C228.369 64.9772 239.719 69.8334 252.219 82.3971C263.233 93.4672 257.678 103.811 250.463 109.859" stroke="white" stroke-width="11.7187" stroke-miterlimit="22.926" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M199.235 80.8274C196.772 78.0307 193.463 75.4655 190.096 74.5596C182.432 72.4965 170.268 74.1102 155.09 82.8571C141.716 90.5639 144.526 102.061 150.001 109.859" stroke="white" stroke-width="11.7187" stroke-miterlimit="22.926" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M59.0215 57.1238H59.1791" stroke="white" stroke-width="11.7187" stroke-miterlimit="2.613" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M276.425 127.436H276.563M235.547 127.435H259.591" stroke="white" stroke-width="11.7187" stroke-miterlimit="2.613" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_4332_1873">
|
||||
<rect width="300" height="213" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@ -1,19 +1,21 @@
|
||||
interface FallbackImageProps {
|
||||
thumbnail: string
|
||||
locationType?: string
|
||||
style: React.CSSProperties
|
||||
style?: React.CSSProperties
|
||||
alt?: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
const fallbackThumbnailSrc = 'https://otherstuff.nochill.in/public/upload/misty-forest-black-white.webp';
|
||||
const restaurantThumbnailSrc = 'https://otherstuff.nochill.in/restaorunta.webp';
|
||||
|
||||
const FallbackImage = ({ thumbnail, locationType, style, alt }: FallbackImageProps) => (
|
||||
const FallbackImage = ({ thumbnail, locationType, style, alt, className }: FallbackImageProps) => (
|
||||
<img
|
||||
src={thumbnail}
|
||||
loading={"lazy"}
|
||||
style={style}
|
||||
alt={alt}
|
||||
className={className}
|
||||
onError={(e) => {
|
||||
if (locationType === 'restaurant' || locationType === 'culinary') {
|
||||
e.currentTarget.src = restaurantThumbnailSrc;
|
||||
|
||||
@ -23,6 +23,7 @@ const GET_LIST_TRENDING_LOCATIONS_URI = `${BASE_URL}/locations/trending`;
|
||||
const GET_LIST_RECENT_LOCATIONS_RATING_URI = `${BASE_URL}/locations/recent`;
|
||||
const GET_LOCATION_URI = `${BASE_URL}/location`;
|
||||
const GET_LOCATION_TAGS_URI = `${BASE_URL}/location/tags`;
|
||||
const GET_LOCATION_REVIEW_GRADES_URI = `${BASE_URL}/location/grades`;
|
||||
const POST_CREATE_LOCATION = `${BASE_URL}/location`;
|
||||
const POST_LOCATION_VISIT_URI = `${BASE_URL}/location`;
|
||||
|
||||
@ -51,6 +52,7 @@ export {
|
||||
GET_LOCATION_URI,
|
||||
GET_SEARCH_LOCATIONS_URI,
|
||||
GET_LOCATION_TAGS_URI,
|
||||
GET_LOCATION_REVIEW_GRADES_URI,
|
||||
GET_IMAGES_BY_LOCATION_URI,
|
||||
GET_CURRENT_USER_REVIEW_LOCATION_URI,
|
||||
PATCH_USER_AVATAR,
|
||||
|
||||
@ -11,8 +11,9 @@ import {
|
||||
CurrentUserLocationReviews,
|
||||
} from './types';
|
||||
import { handleApiError, useAutosizeTextArea } from '../../utils';
|
||||
import { getCurrentUserLocationReviewService, getImagesByLocationService, getLocationService, postReviewLocation, postReviewImages } from "../../services";
|
||||
import { recordLocationVisitService } from "../../services/locations";
|
||||
import { getCurrentUserLocationReviewService, getImagesByLocationService, getLocationService, postReviewLocation, postReviewImages, LocationReviewGrades } from "../../services";
|
||||
import { recordLocationVisitService, getLocationReviewGradesService } from "../../services/locations";
|
||||
import { LocationType } from '../../types/common';
|
||||
import { DefaultSeparator, SeparatorWithAnchor, CustomInterweave, SpinnerLoading, ReviewCard, ReviewCardFull } from '../../components';
|
||||
import RatingsCard from '../../components/Card/RatingsCard';
|
||||
import { useSelector } from 'react-redux';
|
||||
@ -24,6 +25,7 @@ import ReactTextareaAutosize from 'react-textarea-autosize';
|
||||
import { MenuIcon } from '../../../src/components/Icons/MenuIcon';
|
||||
import ScheduleCard from '../../components/Card/ScheduleCard';
|
||||
import FacilitiesCard from '../../components/Card/FacilitiesCard';
|
||||
import FallbackImage from '../../components/Img/FallbackImage';
|
||||
|
||||
|
||||
const SORT_TYPE = [
|
||||
@ -37,6 +39,7 @@ function LocationDetail() {
|
||||
const [locationDetail, setLocationDetail] = useCallbackState<LocationDetailResponse>(EmptyLocationDetailResponse)
|
||||
const [locationImages, setLocationImages] = useState<LocationResponse>(emptyLocationResponse())
|
||||
const [currentUserReview, setCurrentUserReview] = useState<CurrentUserLocationReviews>()
|
||||
const [reviewGrades, setReviewGrades] = useState<LocationReviewGrades | null>(null)
|
||||
const [lightboxOpen, setLightboxOpen] = useState<boolean>(false)
|
||||
const [updatePage, setUpdatePage] = useState<boolean>(true)
|
||||
const [pageState, setPageState] = useState({
|
||||
@ -127,6 +130,12 @@ function LocationDetail() {
|
||||
}
|
||||
}
|
||||
|
||||
async function getLocationReviewGrades(): Promise<void> {
|
||||
const { data, error } = await getLocationReviewGradesService(Number(id))
|
||||
if (error) return
|
||||
setReviewGrades(data)
|
||||
}
|
||||
|
||||
async function getImage(thumbnail?: String): Promise<void> {
|
||||
try {
|
||||
const res = await getImagesByLocationService({ page: 1, page_size: 15, location_id: Number(id) })
|
||||
@ -261,6 +270,12 @@ function LocationDetail() {
|
||||
}
|
||||
}, [updatePage, id])
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
getLocationReviewGrades()
|
||||
}
|
||||
}, [id])
|
||||
|
||||
return (
|
||||
<div className="content main-content mt-3">
|
||||
<section name={"HEADER LINK"}>
|
||||
@ -289,11 +304,17 @@ function LocationDetail() {
|
||||
className={`relative overflow-hidden cursor-zoom-in group/tile ${className}`}
|
||||
onClick={() => openAt(index)}
|
||||
>
|
||||
<img
|
||||
<FallbackImage
|
||||
alt=""
|
||||
thumbnail={img.src}
|
||||
locationType={locationDetail.detail.location_type}
|
||||
className="w-full h-full object-cover transition-all duration-300 group-hover/tile:brightness-75"
|
||||
/>
|
||||
{/* <img
|
||||
src={img.src}
|
||||
alt=""
|
||||
className="w-full h-full object-cover transition-all duration-300 group-hover/tile:brightness-75"
|
||||
/>
|
||||
/> */}
|
||||
<div className="absolute bottom-0 left-0 right-0 p-2 flex justify-between items-end pointer-events-none">
|
||||
<span className="text-white text-xs font-semibold drop-shadow-[0_1px_2px_rgba(0,0,0,0.9)]">
|
||||
Photo {index + 1}
|
||||
@ -406,18 +427,26 @@ function LocationDetail() {
|
||||
score: Number(data.detail.user_score),
|
||||
count: Number(data.detail.user_count)
|
||||
})}
|
||||
getCriticDetails={() => ({
|
||||
environment: 85,
|
||||
cleanliness: 90,
|
||||
price: 75,
|
||||
facility: 80
|
||||
})}
|
||||
getUserDetails={() => ({
|
||||
environment: 82,
|
||||
cleanliness: 88,
|
||||
price: 70,
|
||||
facility: 78
|
||||
})}
|
||||
getCriticDetails={reviewGrades ? () => {
|
||||
const isCulinary = locationDetail.detail.location_type === LocationType.Culinary
|
||||
const g = reviewGrades.critics_grades
|
||||
return [
|
||||
{ label: isCulinary ? 'Taste' : 'Comfort', value: isCulinary ? g.avg_taste_grade : g.avg_comfort_grade },
|
||||
{ label: 'Cleanliness', value: g.avg_cleanliness_grade },
|
||||
{ label: 'Service', value: g.avg_service_grade },
|
||||
{ label: 'Facilities', value: g.avg_facilities_grade },
|
||||
]
|
||||
} : undefined}
|
||||
getUserDetails={reviewGrades ? () => {
|
||||
const isCulinary = locationDetail.detail.location_type === LocationType.Culinary
|
||||
const g = reviewGrades.users_grades
|
||||
return [
|
||||
{ label: isCulinary ? 'Taste' : 'Comfort', value: isCulinary ? g.avg_taste_grade : g.avg_comfort_grade },
|
||||
{ label: 'Cleanliness', value: g.avg_cleanliness_grade },
|
||||
{ label: 'Service', value: g.avg_service_grade },
|
||||
{ label: 'Facilities', value: g.avg_facilities_grade },
|
||||
]
|
||||
} : undefined}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full md:w-[320px] flex-shrink-0">
|
||||
|
||||
@ -3,13 +3,13 @@ import { SlideImage } from "yet-another-react-lightbox"
|
||||
|
||||
export interface ILocationDetail {
|
||||
id: number,
|
||||
name: String,
|
||||
address: String,
|
||||
regency_name: String,
|
||||
province_name: String,
|
||||
location_type: String,
|
||||
region_name: String,
|
||||
google_maps_link: String,
|
||||
name: string,
|
||||
address: string,
|
||||
regency_name: string,
|
||||
province_name: string,
|
||||
location_type: string,
|
||||
region_name: string,
|
||||
google_maps_link: string,
|
||||
thumbnail: string | null,
|
||||
submitted_by: number,
|
||||
critic_score: number,
|
||||
@ -52,7 +52,7 @@ export interface LocationReviewsResponse {
|
||||
|
||||
export interface LocationDetailResponse {
|
||||
detail: ILocationDetail,
|
||||
tags: Array<String>
|
||||
tags: Array<string>
|
||||
users_review: Array<LocationReviewsResponse>,
|
||||
critics_review: Array<LocationReviewsResponse>
|
||||
}
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
import {
|
||||
import {
|
||||
getListLocationsService,
|
||||
getListRecentLocationsRatingsService,
|
||||
getListTopLocationsService,
|
||||
getLocationService,
|
||||
getLocationTagsService,
|
||||
getLocationReviewGradesService,
|
||||
} from "./locations";
|
||||
export type { LocationReviewGrades, LocationReviewGradesGroup } from "./locations";
|
||||
import { getImagesByLocationService } from "./images"
|
||||
import { createAccountService, loginService, logoutService } from "./auth";
|
||||
import { postReviewLocation, postReviewImages, getCurrentUserLocationReviewService } from "./review";
|
||||
@ -28,6 +30,7 @@ export {
|
||||
getListTopLocationsService,
|
||||
getLocationService,
|
||||
getLocationTagsService,
|
||||
getLocationReviewGradesService,
|
||||
getImagesByLocationService,
|
||||
|
||||
postReviewLocation,
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
GET_LIST_TRENDING_LOCATIONS_URI,
|
||||
GET_LOCATION_TAGS_URI,
|
||||
GET_LOCATION_URI,
|
||||
GET_LOCATION_REVIEW_GRADES_URI,
|
||||
GET_SEARCH_LOCATIONS_URI,
|
||||
POST_CREATE_LOCATION,
|
||||
POST_LOCATION_VISIT_URI,
|
||||
@ -173,7 +174,6 @@ async function getListTopLocationsService(params: GetListLocationsArg) {
|
||||
async function getLocationService(params: GetLocationArg) {
|
||||
try {
|
||||
const data = await fetchLocation(params)
|
||||
console.log(data)
|
||||
return { data, error: null }
|
||||
} catch (error) {
|
||||
throw error
|
||||
@ -277,6 +277,39 @@ export const recordLocationVisitService = async (locationId: number): Promise<vo
|
||||
}
|
||||
}
|
||||
|
||||
export interface LocationReviewGradesGroup {
|
||||
avg_taste_grade: number | null
|
||||
taste_grade_count: number
|
||||
avg_comfort_grade: number | null
|
||||
comfort_grade_count: number
|
||||
avg_cleanliness_grade: number | null
|
||||
cleanliness_grade_count: number
|
||||
avg_service_grade: number | null
|
||||
service_grade_count: number
|
||||
avg_facilities_grade: number | null
|
||||
facilities_grade_count: number
|
||||
}
|
||||
|
||||
export interface LocationReviewGrades {
|
||||
critics_grades: LocationReviewGradesGroup
|
||||
users_grades: LocationReviewGradesGroup
|
||||
}
|
||||
|
||||
const fetchLocationReviewGrades = async (locationId: number): Promise<LocationReviewGrades> => {
|
||||
const url = `${GET_LOCATION_REVIEW_GRADES_URI}/${locationId}`
|
||||
const response = await client({ method: 'GET', url })
|
||||
return response.data
|
||||
}
|
||||
|
||||
export const getLocationReviewGradesService = async (locationId: number) => {
|
||||
try {
|
||||
const data = await fetchLocationReviewGrades(locationId)
|
||||
return { data, error: null }
|
||||
} catch (error: any) {
|
||||
return { data: null, error, status: error?.status }
|
||||
}
|
||||
}
|
||||
|
||||
export const getMenuItemsService = async (locationId: number) => {
|
||||
try {
|
||||
const data = await fetchMenuItems(locationId)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user