Created
September 25, 2024 22:18
-
-
Save reinaldoacdc/1b5ec9140800da387deee57228e93ec5 to your computer and use it in GitHub Desktop.
Demanda React
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useCallback, useEffect, useState } from 'react' | |
import { Calendar, SlotInfo, View, Views, momentLocalizer } from 'react-big-calendar' | |
import withDragAndDrop, { EventInteractionArgs } from 'react-big-calendar/lib/addons/dragAndDrop' | |
import useWebSocket, { ReadyState } from 'react-use-websocket' | |
import moment from 'moment-timezone' | |
import { BookingRepository } from '../helpers/bookingRepository' | |
import CustomHeader from './CustomHeader' | |
import AddEventModal from './AddEventModal' | |
import { getColor } from '../helpers/booking' | |
import { Booking, Group } from '../helpers/apiData' | |
import { AgendaEvent, AgendaGroup, AgendaResource, AgendaStaff, CurrentSlot } from '../helpers/calendarData' | |
import BookingModal from './CreateBookingModal' | |
import { Event } from './Event' | |
import { getDayLimits, getSelectedResources } from '../helpers/resourceHelper' | |
import { CustomerView } from './CustomView' | |
export type AgendaViewProps = { | |
shopId: string | |
date: Date | |
dayStart: number | |
dayEnd: number | |
agendaAudit: boolean | |
interval: number | |
selected: AgendaStaff[] | |
selectedGroups: AgendaGroup[] | |
staffs: AgendaStaff[] | |
customer: number | null | |
onChangeDate: (date: Date) => void | |
} | |
const MyAgendaView = (props: AgendaViewProps) => { | |
const [events, setEvents] = useState<AgendaEvent[]>([]) | |
// State to represent the slot clicked | |
const [bookingId, setBookingId] = useState(0) | |
const [openSlot, setOpenSlot] = useState(false) | |
const [openBooking, setOpenBooking] = useState(false) | |
const [currentSlot, setCurrentSlot] = useState<CurrentSlot | null>(null) | |
// | |
const { sendMessage, lastMessage, readyState } = useWebSocket('ws://localhost:3000/cable', { | |
onOpen: () => { | |
console.log('Connected to server') | |
const msg = { | |
command: 'subscribe', | |
identifier: JSON.stringify({ | |
id: 1, | |
channel: 'AgendaChannel' | |
}) | |
} | |
sendMessage(JSON.stringify(msg)) | |
} | |
}) | |
const bookingRepository = new BookingRepository() | |
moment.tz.setDefault('America/Sao_Paulo') | |
const localizer = momentLocalizer(moment) | |
let resourceMap = getSelectedResources(props.selected, props.staffs, props.selectedGroups) | |
let viewtype: View = Views.DAY | |
if (props.customer) { viewtype = Views.AGENDA } | |
// Load Date events on start | |
useEffect(() => { | |
return () => { | |
bookingRepository.loadCalendar(props.date.toDateString(), props.shopId) | |
.then(eventsData => { | |
eventsData.forEach(event => { | |
setEvents(events => [...events, event]) | |
}) | |
}) | |
.catch(console.error) | |
} | |
}, []) | |
// At any time the customer changes, the events are filtered | |
useEffect(() => { | |
if (props.customer) { | |
// Load By Customer | |
setEvents([]) | |
bookingRepository.loadByCustomer(props.customer, props.shopId) | |
.then(eventsData => { | |
console.log('eventdata', eventsData) | |
eventsData.forEach(event => { | |
console.log('event', event) | |
setEvents(events => [...events, event]) }) | |
}) | |
.catch(console.error) | |
} else { | |
setEvents([]) | |
bookingRepository.loadCalendar(props.date.toDateString(), props.shopId) | |
.then(eventsData => { | |
eventsData.forEach(event => { setEvents(events => [...events, event]) }) | |
}) | |
.catch(console.error) | |
} | |
}, [props.customer]) | |
useEffect(() => { | |
if (lastMessage !== null) { | |
const data = JSON.parse(lastMessage.data) | |
if ((data.type === 'ping') || (data.type === 'welcome')) { return } | |
console.log('Recebendo do Ws....', data) | |
//const js = JSON.parse(JSON.parse(data)) | |
const message = data.message | |
if (message) { | |
console.log('js parsed message', typeof (message), message) | |
if (typeof (message) !== 'string') { | |
const mdata = message.data | |
console.log('js parsed data', typeof (mdata), mdata) | |
const content = JSON.parse(mdata.content) | |
console.log('js parsed content', typeof (content), content) | |
// Get the type from data.message and adjust it | |
//const start = new Date(Object.values(content)[1]) | |
//const end = new Date(Object.values(content)[2]) | |
//const resourceId = Object.values(content)[3] | |
//setEvents([...events, { title: 'Pré-Reserva', start, end, resourceId, colorEvento: 'gray', color: 'white' }]) | |
} | |
} | |
} | |
}, [lastMessage]) | |
const handleClose = () => { | |
setCurrentSlot(null) | |
setOpenSlot(false) | |
} | |
const handleBookingClose = () => { | |
setOpenBooking(false) | |
} | |
const onAddEvent = (event :Booking) => { | |
bookingRepository.postCalendar(props.shopId, event) | |
.then(eventData => { setEvents(events => [...events, eventData])}) | |
.catch(console.error) | |
handleClose() | |
} | |
const onUpdateEvent = (event: Booking) => { | |
setEvents((prev :AgendaEvent[]) => { | |
const existing = prev.find((ev) => ev.id === event.id)! | |
const filtered = prev.filter((ev) => ev.id !== event.id) | |
return [...filtered, { ...existing, start: event.start_at, end: event.end_at, resourceId: event.staff_id }] | |
}) | |
} | |
const onCancelEvent = (event: Booking) => { | |
setEvents((prev :AgendaEvent[]) => { | |
const filtered = prev.filter((ev) => ev.id !== event.id) | |
return filtered | |
}) | |
} | |
const DragAndDropCalendar = withDragAndDrop(Calendar) | |
return ( | |
<div> | |
<AddEventModal | |
shopId={props.shopId} | |
slot={currentSlot} | |
agendaAudit={props.agendaAudit} | |
open={openSlot} | |
handleClose={handleClose} | |
onAddEvent={onAddEvent} | |
/> | |
<BookingModal | |
open={openBooking} | |
handleClose={handleBookingClose} | |
shopId={props.shopId} | |
bookingId={bookingId} | |
staffs={props.staffs } | |
agendaAudit={props.agendaAudit} | |
onUpdateEvents={onUpdateEvent} | |
onCancelEvent={onCancelEvent} | |
/> | |
<DragAndDropCalendar | |
date={props.date} | |
defaultView={viewtype} | |
events={events} | |
localizer={localizer} | |
resources={resourceMap} | |
step={props.interval} | |
eventPropGetter={(event) => { return getColor(event) }} | |
min={new Date(new Date().setHours(props.dayStart, 0, 0))} | |
max={new Date(new Date().setHours(props.dayEnd, 0, 0))} | |
style={{ height: 800 }} | |
views={{ day: true, agenda: CustomerView }} | |
slotPropGetter={ (date, resourceId) => { | |
const mystaff = resourceMap.find( (el) => { return el.id == resourceId} ) | |
if(!mystaff){return {} } | |
const limits = getDayLimits(date, mystaff.hours) | |
if(!limits.enabled){return { style: { background: 'pink'} } } | |
const h = date.getHours() | |
const start = new Date(limits.start).getUTCHours() | |
const end = new Date(limits.end).getUTCHours() | |
let bg_color = '' | |
bg_color ="lightgrey" | |
if( (h >= start) && (end > h ) ) {bg_color ="white" } | |
return { | |
style: { | |
background: bg_color | |
} | |
} | |
} } | |
selectable | |
onSelectSlot={ (event: SlotInfo) => { | |
const currentDate = new Date() | |
const yesterday = new Date(currentDate) | |
yesterday.setDate(yesterday.getDate() - 1) | |
yesterday.setHours(23, 59) | |
if (event.start <= yesterday){ return alert('Não é permitido agendar para datas retroativas') } | |
const resource = resourceMap.find(item => item.id === event.resourceId) | |
if(!resource) {return} | |
const resourceId = resource.id | |
const resourceName = `${resource.id} - ${resource.title}` | |
const start_at = event.slots[0]! | |
const end_at = event.slots.at(-1)! | |
setCurrentSlot({ resourceId, resourceName, start_at: new Date(start_at), end_at: new Date(end_at) }) | |
setOpenSlot(true) | |
} } | |
onSelectEvent={ (event: any) => { | |
setBookingId(event.id) | |
setOpenBooking(true) | |
} } | |
onEventDrop={ (args :EventInteractionArgs<any>) => { | |
const resourceId :number = args.resourceId as number | |
if(!resourceId){return} | |
const myEvent = { | |
id: parseInt(args.event.id), | |
staff_id: resourceId, | |
start_at: new Date(args.start), | |
end_at: new Date(args.end), | |
} | |
bookingRepository.putCalendar(props.shopId, myEvent as any) | |
.then( (data) => { | |
setEvents((prev :AgendaEvent[]) => { | |
const existing = prev.find((ev) => ev.id === args.event.id)! | |
const filtered = prev.filter((ev) => ev.id !== args.event.id) | |
return [...filtered, { ...existing, start: myEvent.start_at, end: myEvent.end_at, resourceId: myEvent.staff_id }] | |
}) | |
}) | |
.catch((error) => { | |
if (error.response.data.error) { | |
alert(error.response.data.error) | |
} else { | |
alert(error.message) | |
} | |
} ) | |
} | |
} | |
components={ | |
{ | |
resourceHeader: (props: any) => { | |
return (<CustomHeader name={props.resource.title} photo={props.resource.photo} />) | |
}, | |
//event: Event | |
} | |
} | |
onNavigate={date => { | |
setEvents([]) | |
const myDate = date.toLocaleString("fr-CA") | |
bookingRepository.loadCalendar(myDate, props.shopId) | |
.then(eventsData => { | |
eventsData.forEach(event => { | |
if (props.customer) { | |
if (event.customer_id == props.customer) { setEvents(events => [...events, event]) } | |
} else { | |
setEvents(events => [...events, event]) | |
} | |
}) | |
}) | |
.catch(console.error) | |
props.onChangeDate(date) | |
}} | |
messages={{ today: 'Hoje', previous: '<', next: '>' }} | |
/> | |
</div> | |
) | |
} | |
export default MyAgendaView |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useEffect, useState } from "react"; | |
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider' | |
import { AdapterMoment } from '@mui/x-date-pickers/AdapterMoment' | |
import { DateCalendar } from '@mui/x-date-pickers/DateCalendar' | |
import Select, { SingleValue } from 'react-select' | |
import moment from 'moment-timezone' | |
import { StaffRepository } from "../helpers/staffRepository"; | |
import { CustomerRepository } from "../helpers/customerRepository"; | |
import MyAgendaView from './AgendaView' | |
import { AgendaStaff, AgendaCustomer, AgendaGroup } from "../helpers/calendarData"; | |
import ErrorBoundary from "./ErrorBoundary"; | |
import { Group } from "../helpers/apiData"; | |
type AgendaProps = { | |
shopId: string | |
interval: number | |
agendaAudit: boolean | |
dayStart: number | |
dayEnd: number | |
} | |
const Agenda = (props: AgendaProps) => { | |
const [date, setDate] = useState(new Date()) | |
const [staffs, setStaffs] = useState<AgendaStaff[]>([]) | |
const [selected, setSelected] = useState([]) // specify what | |
const [customers, setCustomers] = useState<AgendaCustomer[]>([]) | |
const [selectedCustomer, setSelectedCustomer] = useState<number | null>(null) | |
const [groups, setGroups] = useState<AgendaGroup[]>([]) | |
const [selectedGroups, setSelectedGroups] = useState([]) | |
useEffect(() => { | |
const staffRepository = new StaffRepository() | |
const customerRepository = new CustomerRepository() | |
staffRepository.loadStaffs(props.shopId) | |
.then(staffList => { | |
staffList.forEach(staff => { | |
if ( (staff.active) && (staff.agenda) ) { | |
setStaffs(staffs => [...staffs, { | |
value: staff.id, | |
label: staff.nickname, | |
avatar: staff.avatar, | |
groupId: staff.groupId, | |
hours: JSON.parse(staff.hours) | |
}]) | |
} | |
}) | |
}) | |
.catch(console.error) | |
staffRepository.loadGroups(props.shopId) | |
.then(list => { | |
list.forEach( (group) => { | |
setGroups( groups => [...groups, { value: group.id, label: group.name } ] ) | |
}) | |
}) | |
.catch(console.error) | |
customerRepository.getCustomers(props.shopId) | |
.then(list => {list.forEach(customer => { | |
setCustomers(customers => [...customers, { | |
value: customer.id, | |
label: customer.name + ' - ' + customer.phone }]) | |
})}) | |
}, []) | |
return ( | |
<> | |
<div className="row"> | |
<div className="col-sm-3"> | |
<div> | |
<LocalizationProvider dateAdapter={AdapterMoment}> | |
<DateCalendar | |
value={moment.utc(date).tz('America/Sao_Paulo')} | |
onChange={(newValue) => { | |
setDate(newValue._d) | |
} | |
} /> | |
</LocalizationProvider> | |
</div> | |
<p>Profissionais</p> | |
<Select | |
isMulti | |
name="staffs" | |
options={staffs} | |
className="basic-multi-select" | |
classNamePrefix="select" | |
onChange={ (newValue: any) => setSelected(newValue) } | |
/> | |
<p>Grupos</p> | |
<Select | |
isMulti | |
name="groups" | |
options={groups} | |
className="basic-multi-select" | |
classNamePrefix="select" | |
onChange={ (newValue: any) => setSelectedGroups(newValue) } | |
/> | |
</div> | |
<div className="col-sm-9"> | |
<Select | |
isSearchable | |
name="customers" | |
options={customers} | |
classNamePrefix="select" | |
isClearable | |
onChange={(newVal :SingleValue<AgendaCustomer> ) => { | |
if (newVal === null) { | |
setSelectedCustomer(null) | |
} else { | |
setSelectedCustomer(newVal.value) | |
} | |
} | |
} | |
/> | |
<ErrorBoundary fallback={<p>Something went wrong</p>}> | |
<MyAgendaView | |
shopId={props.shopId} | |
interval={props.interval} | |
dayStart={props.dayStart} | |
dayEnd={props.dayEnd} | |
agendaAudit={props.agendaAudit} | |
staffs={staffs} | |
selected={selected} | |
selectedGroups={selectedGroups} | |
date={date} | |
customer={selectedCustomer} | |
onChangeDate={(date: Date) => setDate(date)} | |
> | |
</MyAgendaView> | |
</ErrorBoundary> | |
</div> | |
</div> | |
</> | |
); | |
}; | |
export default Agenda; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from "react"; | |
import ReactDOM from "react-dom/client"; | |
import Product from './Product' | |
import Agenda from './Agenda' | |
import mount from './entrypoints/mount' | |
const Hello = () => <h1>Hello from React!</h1>; | |
mount({ | |
Hello, | |
Product, | |
Agenda | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment