home page refactor and customer favorite
parent
9fc567957d
commit
bd2aa6e9b6
@ -1,23 +1,37 @@
|
||||
import React from "react";
|
||||
import { HiX } from "react-icons/hi";
|
||||
import React from 'react'
|
||||
import { HiX } from 'react-icons/hi'
|
||||
|
||||
|
||||
export default function Modal({ isOpen, toggle = () => {}, children, title = "", maxW = '2' }) {
|
||||
export default function Modal({
|
||||
isOpen,
|
||||
toggle = () => {},
|
||||
children,
|
||||
title = '',
|
||||
maxW = '2',
|
||||
}) {
|
||||
return (
|
||||
<div className={`${isOpen ? "" : "hidden "} overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 p-4 w-full md:inset-0 h-modal md:h-full justify-center items-center flex bg-opacity-50 dark:bg-opacity-90 bg-gray-900 dark:bg-gray-900`}>
|
||||
<div
|
||||
className={`${
|
||||
isOpen ? '' : 'hidden '
|
||||
} overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 p-4 w-full md:inset-0 h-modal md:h-full justify-center items-center flex bg-opacity-50 dark:bg-opacity-90 bg-gray-900 dark:bg-gray-900`}
|
||||
>
|
||||
<div className={`relative w-full max-w-${maxW}xl h-full md:h-auto`}>
|
||||
<div className="relative bg-white rounded-lg shadow dark:bg-gray-700 text-base dark:text-gray-400">
|
||||
<div className="flex items-start justify-between rounded-t dark:border-gray-600 p-2">
|
||||
<h3 className="text-xl font-medium text-gray-900 dark:text-white py-2 pl-2">{ title }</h3>
|
||||
<button aria-label="Close" className="ml-auto inline-flex items-center rounded-lg bg-transparent p-1.5 text-sm text-gray-400 hover:bg-gray-200 hover:text-gray-900 dark:hover:bg-gray-600 dark:hover:text-white" type="button" onClick={toggle}>
|
||||
<HiX className="h-5 w-5"/>
|
||||
<h3 className="text-xl font-medium text-gray-900 dark:text-white py-2 pl-2">
|
||||
{title}
|
||||
</h3>
|
||||
<button
|
||||
aria-label="Close"
|
||||
className="ml-auto inline-flex items-center rounded-lg bg-transparent p-1.5 text-sm text-gray-400 hover:bg-gray-200 hover:text-gray-900 dark:hover:bg-gray-600 dark:hover:text-white"
|
||||
type="button"
|
||||
onClick={toggle}
|
||||
>
|
||||
<HiX className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="px-4 pb-4 space-y-2">
|
||||
{children}
|
||||
</div>
|
||||
<div className="px-4 pb-4 space-y-2">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,15 @@
|
||||
export default function Modal({ isOpen, children }) {
|
||||
return (
|
||||
<div
|
||||
className={`${
|
||||
isOpen ? '' : 'hidden '
|
||||
} overflow-y-auto overflow-x-hidden fixed top-0 right-0 z-50 w-full h-full justify-center flex bg-opacity-50 dark:bg-opacity-90 bg-gray-900 dark:bg-gray-900 delay-150 transition ease-in-out duration-1000`}
|
||||
>
|
||||
<div className={`relative w-full max-w-md h-full md:h-auto`}>
|
||||
<div className="relative bg-white h-full p-2 dark:bg-gray-700 text-base dark:text-gray-400">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -0,0 +1,148 @@
|
||||
import React, { useState } from 'react'
|
||||
import { router, usePage } from '@inertiajs/react'
|
||||
import { HiXMark } from 'react-icons/hi2'
|
||||
|
||||
import { useModalState } from '@/hooks'
|
||||
import VoucherCard from '../Partials/VoucherCard'
|
||||
import FormLocation from '../../Components/FormLocation'
|
||||
import LocationModal from '../Partials/LocationModal'
|
||||
|
||||
const EmptyLocation = () => {
|
||||
return (
|
||||
<div className="w-full px-5 text-center flex flex-col my-auto">
|
||||
<div className="font-bold text-xl">Pilih lokasi</div>
|
||||
<div className="text-gray-400">
|
||||
pilih lokasi untuk dapat menampilkan voucher tersedia
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const EmptyVoucher = () => {
|
||||
return (
|
||||
<div className="w-full px-5 text-center flex flex-col my-auto">
|
||||
<div className="font-bold text-xl">Voucher belum tersedia</div>
|
||||
<div className="text-gray-400">
|
||||
sepertinya voucher di lokasimu sedang tidak tersedia
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function AllVoucher() {
|
||||
const {
|
||||
props: {
|
||||
locations,
|
||||
vouchers: { data, next_page_url },
|
||||
_slocations,
|
||||
},
|
||||
} = usePage()
|
||||
|
||||
const locationModal = useModalState()
|
||||
|
||||
const nextPageUrl = next_page_url === undefined ? null : next_page_url
|
||||
const [items, setItems] = useState(data === undefined ? [] : data)
|
||||
const [sLocations, setSLocations] = useState(_slocations)
|
||||
|
||||
const handleAddLocation = (location) => {
|
||||
const isExists = sLocations.find((l) => l.id === location.id)
|
||||
if (!isExists) {
|
||||
const locations = [location].concat(...sLocations)
|
||||
setSLocations(locations)
|
||||
fetch(locations)
|
||||
}
|
||||
}
|
||||
|
||||
const handleRemoveLocation = (index) => {
|
||||
const locations = sLocations.filter((_, i) => i !== index)
|
||||
setSLocations(locations)
|
||||
fetch(locations)
|
||||
}
|
||||
|
||||
const handleNextPage = () => {
|
||||
let location_ids = sLocations.map((l) => l.id)
|
||||
|
||||
router.get(
|
||||
nextPageUrl,
|
||||
{ location_ids: location_ids },
|
||||
{
|
||||
replace: true,
|
||||
preserveState: true,
|
||||
only: ['vouchers'],
|
||||
onSuccess: (res) => {
|
||||
if (res.props.vouchers.data !== undefined) {
|
||||
setItems(items.concat(res.props.vouchers.data))
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const fetch = (locations) => {
|
||||
let location_ids = locations.map((l) => l.id)
|
||||
|
||||
router.get(
|
||||
route(route().current()),
|
||||
{ location_ids: location_ids },
|
||||
{
|
||||
replace: true,
|
||||
preserveState: true,
|
||||
onSuccess: (res) => {
|
||||
if (res.props.vouchers.data !== undefined) {
|
||||
setItems(res.props.vouchers.data)
|
||||
return
|
||||
}
|
||||
setItems([])
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="w-full space-x-2 px-4 mt-2"
|
||||
onClick={locationModal.toggle}
|
||||
>
|
||||
<FormLocation placeholder="Cari Lokasi" />
|
||||
</div>
|
||||
<div className="w-full flex flex-row overflow-y-scroll space-x-2 px-4 mt-2">
|
||||
{sLocations.map((location, index) => (
|
||||
<div
|
||||
className="flex flex-row items-center gap-1 px-2 py-1 rounded-2xl bg-blue-100 border border-blue-200"
|
||||
key={location.id}
|
||||
onClick={() => handleRemoveLocation(index)}
|
||||
>
|
||||
<div>{location.name}</div>
|
||||
<div className="pl-2">
|
||||
<HiXMark className="h-5 w-5 text-red-700" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{items.length <= 0 && sLocations.length <= 0 && <EmptyLocation />}
|
||||
|
||||
{/* voucher */}
|
||||
<div className="flex flex-col w-full px-3 mt-3 space-y-2">
|
||||
{items.map((voucher) => (
|
||||
<VoucherCard key={voucher.id} voucher={voucher} />
|
||||
))}
|
||||
{nextPageUrl !== null && (
|
||||
<div
|
||||
onClick={handleNextPage}
|
||||
className="w-full text-center px-2 py-1 border mt-5 hover:bg-blue-600 hover:text-white"
|
||||
>
|
||||
Load more
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{items.length <= 0 && sLocations.length > 0 && <EmptyVoucher />}
|
||||
|
||||
<LocationModal
|
||||
state={locationModal}
|
||||
locations={locations}
|
||||
onItemSelected={handleAddLocation}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { router, usePage } from '@inertiajs/react'
|
||||
import { HiOutlineStar } from 'react-icons/hi2'
|
||||
|
||||
import VoucherCard from '../Partials/VoucherCard'
|
||||
|
||||
const EmptyFavorite = () => {
|
||||
return (
|
||||
<div className="w-full px-5 text-center flex flex-col my-auto">
|
||||
<div className="font-bold text-xl">Favorite kosong</div>
|
||||
<div className="text-gray-400">
|
||||
pilih lokasi favorite mu ya, cek bintangnya
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const EmptyVoucher = () => {
|
||||
return (
|
||||
<div className="w-full px-5 text-center flex flex-col my-auto">
|
||||
<div className="font-bold text-xl">Voucher belum tersedia</div>
|
||||
<div className="text-gray-400">
|
||||
sepertinya voucher di lokasimu sedang tidak tersedia
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function FavoriteVoucher() {
|
||||
const {
|
||||
props: {
|
||||
vouchers: { data, next_page_url },
|
||||
_flocations,
|
||||
},
|
||||
} = usePage()
|
||||
|
||||
const nextPageUrl = next_page_url === undefined ? null : next_page_url
|
||||
const [items, setItems] = useState(data === undefined ? [] : data)
|
||||
|
||||
const handleRemoveLocation = (location) => {
|
||||
router.post(
|
||||
route('customer.location.favorite', location),
|
||||
{},
|
||||
{
|
||||
onSuccess: () => {
|
||||
router.visit(route(route().current()))
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const handleNextPage = () => {
|
||||
router.get(
|
||||
nextPageUrl,
|
||||
{},
|
||||
{
|
||||
replace: true,
|
||||
preserveState: true,
|
||||
only: ['vouchers'],
|
||||
onSuccess: (res) => {
|
||||
if (res.props.vouchers.data !== undefined) {
|
||||
setItems(items.concat(res.props.vouchers.data))
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setItems(data)
|
||||
}, [_flocations])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-full flex flex-row overflow-y-scroll space-x-2 px-4 mt-2">
|
||||
{_flocations.map((location) => (
|
||||
<div
|
||||
className="flex flex-row items-center gap-1 px-2 py-1 rounded-2xl bg-blue-100 border border-blue-200"
|
||||
key={location.id}
|
||||
onClick={() => handleRemoveLocation(location)}
|
||||
>
|
||||
<div>{location.name}</div>
|
||||
<div className="pl-2">
|
||||
<HiOutlineStar className="h-5 w-5 text-yellow-300 fill-yellow-300" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{_flocations.length <= 0 && <EmptyFavorite />}
|
||||
|
||||
{/* voucher */}
|
||||
<div className="flex flex-col w-full px-3 mt-3 space-y-2">
|
||||
{items.map((voucher) => (
|
||||
<VoucherCard key={voucher.id} voucher={voucher} />
|
||||
))}
|
||||
{nextPageUrl !== null && (
|
||||
<div
|
||||
onClick={handleNextPage}
|
||||
className="w-full text-center px-2 py-1 border mt-5 hover:bg-blue-600 hover:text-white"
|
||||
>
|
||||
Load more
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{items.length <= 0 && <EmptyVoucher />}
|
||||
</>
|
||||
)
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { router, usePage } from '@inertiajs/react'
|
||||
import { HiArrowLeft, HiOutlineStar } from 'react-icons/hi2'
|
||||
|
||||
import { useAutoFocus } from '@/hooks'
|
||||
import { isFavorite } from '../utils'
|
||||
import FormLocation from '../../Components/FormLocation'
|
||||
import Modal from '../../Components/Modal'
|
||||
|
||||
export default function LocationModal(props) {
|
||||
const {
|
||||
props: {
|
||||
auth: { user },
|
||||
},
|
||||
} = usePage()
|
||||
const { state, locations, onItemSelected } = props
|
||||
|
||||
const [search, setSearch] = useState('')
|
||||
const locationFocus = useAutoFocus()
|
||||
const [filter_locations, setFilterLocations] = useState(locations)
|
||||
|
||||
const handleOnFilter = (e) => {
|
||||
setSearch(e.target.value)
|
||||
if (e.target.value === '') {
|
||||
setFilterLocations(locations)
|
||||
return
|
||||
}
|
||||
setFilterLocations(
|
||||
filter_locations.filter((location) => {
|
||||
let name = location.name.toLowerCase()
|
||||
let search = e.target.value.toLowerCase()
|
||||
return name.includes(search)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const handleItemSelected = (location) => {
|
||||
onItemSelected(location)
|
||||
state.toggle()
|
||||
}
|
||||
|
||||
const handleFavorite = (location) => {
|
||||
router.post(route('customer.location.favorite', location))
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (state.isOpen === true) {
|
||||
locationFocus.current.focus()
|
||||
}
|
||||
}, [state])
|
||||
|
||||
return (
|
||||
<Modal isOpen={state.isOpen}>
|
||||
<div className="flex flex-row items-center mb-4">
|
||||
<div className="pr-2 py-2" onClick={state.toggle}>
|
||||
<HiArrowLeft className="w-6 h-6" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<FormLocation
|
||||
placeholder="Cari Lokasi"
|
||||
ref={locationFocus}
|
||||
value={search}
|
||||
onChange={handleOnFilter}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col overflow-y-auto max-h-[80vh] bg-white">
|
||||
{filter_locations.map((location) => (
|
||||
<div
|
||||
className="flex flex-row justify-between items-center"
|
||||
key={location.id}
|
||||
>
|
||||
<div
|
||||
onClick={() => handleItemSelected(location)}
|
||||
className="flex-1 px-3 py-3"
|
||||
>
|
||||
{location.name}
|
||||
</div>
|
||||
<div
|
||||
className={`px-3 py-2 ${
|
||||
user === null ? 'hidden' : ''
|
||||
}`}
|
||||
onClick={() => handleFavorite(location)}
|
||||
>
|
||||
<HiOutlineStar
|
||||
className={`w-7 h-7 ${
|
||||
isFavorite(user, location.id)
|
||||
? 'text-yellow-300 fill-yellow-300'
|
||||
: 'text-gray-300'
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
import { router } from '@inertiajs/react'
|
||||
|
||||
export const ALL = 0
|
||||
export const FAVORITE = 1
|
||||
|
||||
export const handleBanner = (banner) => {
|
||||
router.get(route('home.banner', banner))
|
||||
}
|
||||
|
||||
export const isFavorite = (user, id) => {
|
||||
if (user === null) {
|
||||
return false
|
||||
}
|
||||
const isExists = user.location_favorites.findIndex((f) => f.id === id)
|
||||
if (isExists !== -1) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
rsync -arP -e 'ssh -p 225' --exclude=node_modules --exclude=database/database.sqlite --exclude=.git --exclude=.env --exclude=public/hot . arm@ajikamaludin.id:/home/arm/projects/www/voucher
|
||||
|
||||
ssh -p 225 arm@ajikamaludin.id -C docker exec php82 php /var/www/voucher/artisan migrate:refresh --seed
|
Loading…
Reference in New Issue