39443-vm/frontend/src/pages/properties/properties-view.tsx
2026-04-03 06:29:03 +00:00

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;