Skip to content

Instantly share code, notes, and snippets.

@yantakus
Created June 9, 2020 15:19
Show Gist options
  • Save yantakus/f73671ff2fc452d2ba71af6f14980890 to your computer and use it in GitHub Desktop.
Save yantakus/f73671ff2fc452d2ba71af6f14980890 to your computer and use it in GitHub Desktop.
import React, {
ReactElement,
FunctionComponent,
useState,
useMemo,
} from 'react'
import { message, Col, Row, Avatar, Button, Icon, Tabs } from 'antd'
import { map, filter, get, isEmpty } from 'lodash'
import { Spin } from 'antd'
import copy from 'copy-to-clipboard'
import { ReactComponent as BlankIcon } from 'icons/blank.svg'
import {
useOfficeQuery,
OfficeAmenity,
OfficeSuite,
Contact,
MonthlyQuotingRateCondition,
Operator,
Agent,
useOfficeTransportAndContactQuery,
ContactViewFragment,
OfficeModalAvailabilityFragment,
} from 'generated'
import {
SingleMarkerMap,
ToggleInShortlist,
Amenities,
Transport,
AddressLine,
OfficeGallery,
EditPricingForm,
Floorplans,
PremiumFeature,
} from 'components'
import { TransportFragment } from 'components/Transport'
import { rangeToString, formatPrice, formatDateToMonths } from 'utils'
import { quotingRateUnits } from 'constants/units'
import {
StyledContent,
StyledOperatorName,
StyledOfficeName,
StyledOperatorLink,
StyledSidebarWrapper,
StyledSidebarInfo,
StyledSidebarContacts,
StyledContact,
StyledCTACol,
StyledAvailabilityPanel,
} from './styles'
import OfficeSuiteCard from 'components/OfficeSuiteCard'
import gql from 'graphql-tag'
type AvailabilityProps = {
availability: OfficeModalAvailabilityFragment
officeSuites: Pick<OfficeSuite, 'id'>[]
className: string
onShowAll: () => void
}
const Availability: React.FunctionComponent<AvailabilityProps> = ({
availability,
officeSuites,
className,
onShowAll,
}) => {
if (isEmpty(officeSuites)) return null
const availableOfficeSuitesCount = availability?.availableOfficeSuitesCount
const availableFrom = availability?.availableFrom
const monthsNum = formatDateToMonths(
new Date(Date.now()),
new Date(availableFrom),
)
const isPlural = availableOfficeSuitesCount > 1
return (
<StyledAvailabilityPanel className={className}>
<div className="font-semibold">
{availableOfficeSuitesCount ? (
<>
{availableOfficeSuitesCount} Office Suite
{isPlural && 's'} {isPlural ? 'are' : 'is'}{' '}
<span className="text-green-600">available</span>
</>
) : availableFrom ? (
`The earliest Office Suite is available in ${monthsNum} month${
monthsNum > 1 ? 's' : ''
}`
) : (
'All office suites are occupied.'
)}
</div>
<Button className="p-0" type="link" onClick={onShowAll}>
Show all
</Button>
</StyledAvailabilityPanel>
)
}
type ContactProps = {
contact: ContactViewFragment
defaultAvatar?: string
}
const ContactView: FunctionComponent<ContactProps> = ({
contact,
defaultAvatar,
}) => (
<Row className="p-4" type="flex" align="middle">
<Avatar className="mr-2" src={contact?.avatarUrl ?? defaultAvatar} />
<StyledContact>
<div>{contact?.name}</div>
<span className="text-blue-600">{contact?.emails?.[0]?.value}</span>
</StyledContact>
</Row>
)
export const fragments = {
availabilityFragment: gql`
fragment OfficeModalAvailability on OfficeAvailability {
availableOfficeSuitesCount
availableFrom
}
`,
transportFragment: TransportFragment,
contactViewFragment: gql`
fragment ContactView on Contact {
id
name
avatarUrl
emails {
id
value
}
}
`,
}
type ContactsProps = {
contacts?: ContactViewFragment[]
defaultAvatar?: string
}
const Contacts: FunctionComponent<ContactsProps> = ({
contacts,
defaultAvatar,
}) => {
return contacts?.length ? (
<StyledSidebarContacts>
<h3 className="p-4 pb-0">
<strong>Contacts</strong>
</h3>
<ContactView contact={contacts?.[0]} defaultAvatar={defaultAvatar} />
</StyledSidebarContacts>
) : null
}
type MonthlyQuotingRateProps = {
monthlyQuotingRate: MonthlyQuotingRateCondition
}
const MonthlyQuotingRate: React.FunctionComponent<MonthlyQuotingRateProps> = ({
monthlyQuotingRate,
}: MonthlyQuotingRateProps) => {
if (!monthlyQuotingRate?.from && !monthlyQuotingRate?.to) {
return null
}
return (
<>
<small>Monthly quoting rate</small>
<div>
{rangeToString({
data: monthlyQuotingRate,
units: quotingRateUnits,
formatFn: formatPrice,
format: monthlyQuotingRate?.currency,
})}
</div>
</>
)
}
export const OfficeTransportQuery = gql`
query OfficeTransportAndContact($where: OfficeWhereUniqueInput!) {
office(where: $where) {
id
availability {
...OfficeModalAvailability
}
contacts {
...ContactView
}
operator {
id
contacts {
...ContactView
}
}
address {
id
location {
id
transport {
...TransportView
}
}
}
}
}
${fragments.transportFragment}
${fragments.contactViewFragment}
${fragments.availabilityFragment}
`
type OfficeProps = {
officeId: number
showControls?: boolean
contact?: Contact
company?: Operator | Agent
mapPinUrl?: string
mapPinSize?: [number, number]
onError: (error: Error) => void
}
const OfficeView: FunctionComponent<OfficeProps> = ({
officeId,
showControls = true,
company = null,
mapPinUrl = null,
mapPinSize = [48, 61],
onError,
}) => {
const [editPricing, setEditPricing] = useState(false)
const [activeTab, setActiveTab] = useState('Overview')
const { data, error, loading, refetch } = useOfficeQuery({
variables: {
where: { id: Number(officeId) },
},
errorPolicy: 'all',
})
const { data: additionalData } = useOfficeTransportAndContactQuery({
variables: {
where: { id: Number(officeId) },
},
})
const office = get(data, 'office')
const operatorMapPinUrl = get(office, ['operator', 'mapPin'])
const operatorIsPayingCustomer = get(
office,
['operator', 'isPayingCustomer'],
false,
)
const monthlyQuotingRate = get(office, 'monthlyQuotingRate')
const amenities = filter(get(office, 'amenities', [] as OfficeAmenity[]), {
isEnabled: true,
})
const gallery = useMemo(
(): ReactElement => (
<OfficeGallery
entity={office}
entityType="office"
bucketPath="office"
loading={loading}
refetchEntity={refetch}
/>
),
[loading, officeId],
)
const handleLinkCopy = (): void => {
copy(window.location.href)
message.success('The link has been successfully copied to your clipboard.')
}
if (error) {
onError(error)
}
return (loading && !office && !error) ||
get(office, 'id', null) !== Number(officeId) ? (
<Row type="flex" align="middle" justify="center" style={{ height: 600 }}>
<Spin size="large" />
</Row>
) : (
<>
{gallery}
<StyledContent>
<Row className="mb-2">
<Avatar
size="large"
className="mr-2"
src={
company ? get(company, 'logo') : get(office, ['operator', 'logo'])
}
alt={
company ? get(company, 'name') : get(office, ['operator', 'name'])
}
/>
<StyledOperatorName>
{company ? get(company, 'name') : get(office, ['operator', 'name'])}
</StyledOperatorName>
</Row>
<StyledOfficeName>{get(office, 'name')}</StyledOfficeName>
{showControls && operatorIsPayingCustomer && (
<>
<BlankIcon className="mr-2 inline-block" />
<StyledOperatorLink
className={'text-blue-600'}
target="_blank"
href={get(office, 'websiteURL')}
>
{showControls
? 'View this building on the Operator’s website'
: 'View on Site'}
</StyledOperatorLink>
</>
)}
<Tabs activeKey={activeTab} onChange={setActiveTab}>
<Tabs.TabPane tab="Overview" key="Overview">
<Row gutter={30}>
<Col span={16}>
<p className="mb-4">{get(office, 'description')}</p>
<Availability
className="mb-5"
availability={additionalData?.office?.availability}
officeSuites={office?.officeSuites}
onShowAll={() => setActiveTab('Availability')}
/>
<Floorplans floorplans={office.floorplans} />
<Amenities className="mb-6" amenities={amenities} />
<Transport
className="mb-2"
transport={
additionalData?.office?.address?.location?.transport
}
/>
<div style={{ height: 400 }}>
<SingleMarkerMap
lat={get(office, ['address', 'location', 'latitude'])}
lng={get(office, ['address', 'location', 'longitude'])}
icon={{
url: mapPinUrl || operatorMapPinUrl,
scaledSize: new window.google.maps.Size(...mapPinSize),
}}
/>
</div>
</Col>
<StyledCTACol span={8}>
<StyledSidebarWrapper>
<StyledSidebarInfo>
<StyledOfficeName className="mb-1">
{get(office, 'name')}
</StyledOfficeName>
<Row
className="text-blue-600 mb-4 cursor-pointer"
type="flex"
align="middle"
gutter={6}
onClick={handleLinkCopy}
>
<Col>
<Icon type="copy" />
</Col>
<Col>Copy Sharable Link</Col>
</Row>
<small>Address</small>
<AddressLine className="mb-4" address={office?.address} />
{editPricing ? (
<EditPricingForm
officeId={get(office, 'id')}
onClose={() => setEditPricing(false)}
refetchOffice={refetch}
initialValue={monthlyQuotingRate}
/>
) : (
<MonthlyQuotingRate
monthlyQuotingRate={monthlyQuotingRate}
/>
)}
{showControls && (
<ToggleInShortlist
className="mt-4 w-full"
officeId={officeId}
/>
)}
</StyledSidebarInfo>
{/* Removed contacts section from the agent interface
Agreed to limit amount of operator info that served to Agents
<Contacts
contacts={
additionalData?.office?.contacts ??
additionalData?.office?.operator?.contacts
}
defaultAvatar={office?.operator?.logo}
/> */}
</StyledSidebarWrapper>
</StyledCTACol>
</Row>
</Tabs.TabPane>
{!isEmpty(office.officeSuites) && (
<Tabs.TabPane
tab={<PremiumFeature>Availability</PremiumFeature>}
key="Availability"
>
{map(office.officeSuites, officeSuite => (
<OfficeSuiteCard
key={officeSuite.id}
className="mb-4"
officeSuite={officeSuite}
/>
))}
</Tabs.TabPane>
)}
</Tabs>
</StyledContent>
</>
)
}
export default OfficeView
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment