216 lines
9.3 KiB
TypeScript
216 lines
9.3 KiB
TypeScript
import React, { ReactElement, useEffect } from 'react';
|
|
import Head from 'next/head';
|
|
import { mdiHomeCity } from '@mdi/js';
|
|
import { useRouter } from 'next/router';
|
|
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
|
import { fetch } from '../../stores/properties/propertiesSlice';
|
|
import ImageField from '../../components/ImageField';
|
|
import LayoutAuthenticated from '../../layouts/Authenticated';
|
|
import { getPageTitle } from '../../config';
|
|
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
|
|
import SectionMain from '../../components/SectionMain';
|
|
import CardBox from '../../components/CardBox';
|
|
import BaseButton from '../../components/BaseButton';
|
|
import BaseButtons from '../../components/BaseButtons';
|
|
import { hasPermission } from '../../helpers/userPermissions';
|
|
|
|
type StatProps = {
|
|
label: string;
|
|
value: string | number;
|
|
hint?: string;
|
|
};
|
|
|
|
type RelatedListProps = {
|
|
title: string;
|
|
items: any[];
|
|
getLabel: (item: any) => string;
|
|
emptyLabel?: string;
|
|
};
|
|
|
|
const DetailStat = ({ label, value, hint }: StatProps) => (
|
|
<CardBox className="rounded-2xl border border-white/10 shadow-none">
|
|
<div className="space-y-2">
|
|
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-slate-400">{label}</p>
|
|
<p className="text-2xl font-semibold text-white">{value}</p>
|
|
{hint && <p className="text-sm text-slate-400">{hint}</p>}
|
|
</div>
|
|
</CardBox>
|
|
);
|
|
|
|
const DetailRow = ({ label, value }: { label: string; value?: React.ReactNode }) => (
|
|
<div className="rounded-2xl border border-white/10 bg-white/5 px-4 py-3">
|
|
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-slate-400">{label}</p>
|
|
<div className="mt-2 text-sm leading-6 text-slate-100">{value || 'Not set'}</div>
|
|
</div>
|
|
);
|
|
|
|
const RelatedList = ({ title, items, getLabel, emptyLabel = 'No items yet' }: RelatedListProps) => (
|
|
<div className="rounded-2xl border border-white/10 bg-white/5 px-4 py-4">
|
|
<div className="mb-3 flex items-center justify-between gap-3">
|
|
<p className="text-sm font-semibold text-white">{title}</p>
|
|
<span className="rounded-full border border-white/10 px-2.5 py-1 text-xs text-slate-300">
|
|
{items.length}
|
|
</span>
|
|
</div>
|
|
|
|
{items.length ? (
|
|
<div className="space-y-2">
|
|
{items.slice(0, 4).map((item: any) => (
|
|
<div key={item.id} className="rounded-xl border border-white/10 bg-slate-950/40 px-3 py-2 text-sm text-slate-200">
|
|
{getLabel(item)}
|
|
</div>
|
|
))}
|
|
{items.length > 4 && (
|
|
<p className="text-xs uppercase tracking-[0.18em] text-slate-400">+{items.length - 4} more</p>
|
|
)}
|
|
</div>
|
|
) : (
|
|
<p className="text-sm text-slate-400">{emptyLabel}</p>
|
|
)}
|
|
</div>
|
|
);
|
|
|
|
const PropertiesView = () => {
|
|
const router = useRouter();
|
|
const dispatch = useAppDispatch();
|
|
const { properties } = useAppSelector((state) => state.properties);
|
|
const { currentUser } = useAppSelector((state) => state.auth);
|
|
const { id } = router.query;
|
|
|
|
useEffect(() => {
|
|
dispatch(fetch({ id }));
|
|
}, [dispatch, id]);
|
|
|
|
const canEdit = currentUser && hasPermission(currentUser, 'UPDATE_PROPERTIES');
|
|
const unitsCount = Array.isArray(properties?.units) ? properties.units.length : 0;
|
|
const unitTypesCount = Array.isArray(properties?.unit_types) ? properties.unit_types.length : 0;
|
|
const amenitiesCount = Array.isArray(properties?.amenities) ? properties.amenities.length : 0;
|
|
|
|
return (
|
|
<>
|
|
<Head>
|
|
<title>{getPageTitle('Property')}</title>
|
|
</Head>
|
|
|
|
<SectionMain>
|
|
<SectionTitleLineWithButton icon={mdiHomeCity} title="Property" main>
|
|
<BaseButtons noWrap>
|
|
<BaseButton href="/properties/properties-list" color="whiteDark" outline label="Back" />
|
|
{canEdit && <BaseButton color="info" label="Edit" href={`/properties/properties-edit/?id=${id}`} />}
|
|
</BaseButtons>
|
|
</SectionTitleLineWithButton>
|
|
|
|
<div className="mb-6 grid gap-4 md:grid-cols-2 xl:grid-cols-4">
|
|
<DetailStat label="Property" value={properties?.name || 'Untitled'} hint={properties?.code || 'Code not set'} />
|
|
<DetailStat label="Location" value={properties?.city || 'Unassigned'} hint={properties?.country || 'Country not set'} />
|
|
<DetailStat label="Inventory" value={unitsCount} hint={`${unitTypesCount} unit types`} />
|
|
<DetailStat label="Status" value={properties?.is_active ? 'Active' : 'Inactive'} hint={`${amenitiesCount} amenities`} />
|
|
</div>
|
|
|
|
<div className="grid gap-6 xl:grid-cols-[minmax(0,1.4fr)_minmax(320px,0.9fr)]">
|
|
<div className="space-y-6">
|
|
<CardBox className="rounded-2xl border border-white/10 shadow-none">
|
|
<div className="space-y-4">
|
|
<div>
|
|
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-slate-400">Overview</p>
|
|
<h2 className="mt-2 text-xl font-semibold text-white">Property profile</h2>
|
|
<p className="mt-1 text-sm text-slate-400">
|
|
The location, operating status, and core identifying information for the property.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="grid gap-3 md:grid-cols-2">
|
|
<DetailRow label="Tenant" value={properties?.tenant?.name} />
|
|
<DetailRow label="Code" value={properties?.code} />
|
|
<DetailRow label="Address" value={properties?.address} />
|
|
<DetailRow label="Timezone" value={properties?.timezone} />
|
|
<DetailRow label="Country" value={properties?.country} />
|
|
<DetailRow label="Active" value={properties?.is_active ? 'Yes' : 'No'} />
|
|
</div>
|
|
</div>
|
|
</CardBox>
|
|
|
|
<CardBox className="rounded-2xl border border-white/10 shadow-none">
|
|
<div className="space-y-4">
|
|
<div>
|
|
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-slate-400">Description</p>
|
|
<h2 className="mt-2 text-xl font-semibold text-white">What makes this property unique</h2>
|
|
</div>
|
|
|
|
{properties?.description ? (
|
|
<div
|
|
className="text-sm leading-6 text-slate-100"
|
|
dangerouslySetInnerHTML={{ __html: properties.description }}
|
|
/>
|
|
) : (
|
|
<p className="text-sm text-slate-400">No property description has been added yet.</p>
|
|
)}
|
|
</div>
|
|
</CardBox>
|
|
</div>
|
|
|
|
<div className="space-y-6">
|
|
<CardBox className="rounded-2xl border border-white/10 shadow-none">
|
|
<div className="space-y-4">
|
|
<div>
|
|
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-slate-400">Gallery</p>
|
|
<h2 className="mt-2 text-xl font-semibold text-white">Property imagery</h2>
|
|
</div>
|
|
|
|
{Array.isArray(properties?.images) && properties.images.length ? (
|
|
<ImageField name={'images'} image={properties.images} className="h-24 w-24 rounded-2xl" />
|
|
) : (
|
|
<p className="text-sm text-slate-400">No images uploaded yet.</p>
|
|
)}
|
|
</div>
|
|
</CardBox>
|
|
|
|
<CardBox className="rounded-2xl border border-white/10 shadow-none">
|
|
<div className="space-y-4">
|
|
<div>
|
|
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-slate-400">Related Records</p>
|
|
<h2 className="mt-2 text-xl font-semibold text-white">Inventory and demand</h2>
|
|
</div>
|
|
|
|
<div className="space-y-3">
|
|
<RelatedList
|
|
title="Organizations"
|
|
items={Array.isArray(properties?.organizations) ? properties.organizations : []}
|
|
getLabel={(item) => item?.name || item?.id}
|
|
/>
|
|
<RelatedList
|
|
title="Unit Types"
|
|
items={Array.isArray(properties?.unit_types) ? properties.unit_types : []}
|
|
getLabel={(item) => item?.name || item?.code || item?.id}
|
|
/>
|
|
<RelatedList
|
|
title="Units"
|
|
items={Array.isArray(properties?.units) ? properties.units : []}
|
|
getLabel={(item) => item?.name || item?.unit_number || item?.id}
|
|
/>
|
|
<RelatedList
|
|
title="Amenities"
|
|
items={Array.isArray(properties?.amenities) ? properties.amenities : []}
|
|
getLabel={(item) => item?.name || item?.id}
|
|
/>
|
|
<RelatedList
|
|
title="Reservations"
|
|
items={Array.isArray(properties?.reservations_property) ? properties.reservations_property : []}
|
|
getLabel={(item) => item?.reservation_code || item?.status || item?.id}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</CardBox>
|
|
</div>
|
|
</div>
|
|
</SectionMain>
|
|
</>
|
|
);
|
|
};
|
|
|
|
PropertiesView.getLayout = function getLayout(page: ReactElement) {
|
|
return <LayoutAuthenticated permission={'READ_PROPERTIES'}>{page}</LayoutAuthenticated>;
|
|
};
|
|
|
|
export default PropertiesView;
|