Created
June 9, 2020 15:19
-
-
Save yantakus/f73671ff2fc452d2ba71af6f14980890 to your computer and use it in GitHub Desktop.
This file contains 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, { | |
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