Skip to content

Instantly share code, notes, and snippets.

@reinaldoacdc
Created September 25, 2024 22:18
Show Gist options
  • Save reinaldoacdc/1b5ec9140800da387deee57228e93ec5 to your computer and use it in GitHub Desktop.
Save reinaldoacdc/1b5ec9140800da387deee57228e93ec5 to your computer and use it in GitHub Desktop.
Demanda React
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
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;
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