Skip to content

Commit

Permalink
wip: add breadcrumbs to edit page
Browse files Browse the repository at this point in the history
  • Loading branch information
viet nguyen committed Nov 19, 2023
1 parent 3ca5c48 commit 8a91296
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 39 deletions.
25 changes: 22 additions & 3 deletions src/app/editArea/[slug]/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,33 @@
import { ArrowUUpLeft } from '@phosphor-icons/react/dist/ssr'
import { SidebarNav } from './SidebarNav'
import { getPageDataForEdit } from './general/page'
import { GluttenFreeCrumbs } from '@/components/ui/BreadCrumbs'

export default function RootLayout ({
/**
* Layout for edit area dashboard
*/
export default async function RootLayout ({
children, params
}: {
children: React.ReactNode
params: { slug: string }
}): any {
}): Promise<any> {
const { area: { areaName, uuid, pathTokens, ancestors } } = await getPageDataForEdit(params.slug)
return (
<div>
<h1 className='px-12 text-4xl tracking-tight py-12 block'>Edit area</h1>
<div className='px-12 pt-8 pb-4'>
<div className='flex items-center justify-between'>
<h1 className='text-4xl tracking-tight'>Edit area <span className='text-secondary'>{areaName}</span>
</h1>
</div>
<GluttenFreeCrumbs pathTokens={pathTokens} ancestors={ancestors} />
<div className='text-sm flex justify-end'>
<a href={`/area/${uuid}`} className='flex items-center gap-2'>
Return to public version <ArrowUUpLeft size={18} />
</a>
</div>
</div>

<hr className='border-1' />
<div className='pt-12 flex bg-base-200 flex-col lg:flex-row'>
<SidebarNav slug={params.slug} />
Expand Down
79 changes: 54 additions & 25 deletions src/app/editArea/[slug]/manage/components/AreaItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,67 +10,96 @@ type EType = 'area' | 'crag' | 'boulder' | 'climb'

const CragIcon = forwardRef<any, IconProps>((props, ref) => <ShareNetwork ref={ref} {...props} className='p-1 rotate-90' />)

// type MyIconProps = Icon & {
// class
// }
const IconMap: Record<EType, Icon> = {
area: Graph,
crag: CragIcon,
boulder: Cube,
climb: LineSegments
}

export const EntityIcon: React.FC<{ type: EType, withLabel?: boolean }> = ({ type, withLabel = true }) => {
export const EntityIcon: React.FC<{ type: EType, withLabel?: boolean, className?: string }> = ({ type, withLabel = true, className = '' }) => {
const IconComponent = IconMap?.[type]
if (IconComponent == null) return null
return (
<div className='flex gap-1.5 items-center'>
<IconComponent size={20} weight='duotone' />
<span className='text-xs text-secondary font-light'>{type.toUpperCase()}</span>
<IconComponent size={20} weight='duotone' className={className} />
{withLabel && <span className='text-xs font-light'>{type.toUpperCase()}</span>}
</div>
)
}

export const AreaItem: React.FC<{ area: AreaType, parentUuid: string, index: number }> = ({ area, index, parentUuid }) => {
const { uuid, areaName } = area
const { uuid, areaName, children, climbs } = area

// undefined array can mean we forget to include the field in GQL so let's make it not editable
const canDelete = (children?.length ?? 1) === 0 && (climbs?.length ?? 1) === 0

return (
<div className='card card-compact w-full bg-base-100 shadow break-inside-avoid-column break-inside-avoid mb-4 p-2'>
<div className='flex items-center gap-4 justify-between'>
<div><div className='area-entity-box'>{index}</div></div>
<div className='text-sm grow'>
<div>{areaName}</div>
<AreaIcon area={area} />
<div className='break-inside-avoid-column break-inside-avoid pb-8'>
<div className='card card-compact card-bordered border-base-300/80 w-full bg-base-100 shadow p-2'>
<div className='flex items-center gap-4 justify-between'>
<div>
<div className='area-entity-box'>{index}</div>
</div>
<div className='grow space-y-2'>
<Link
className='uppercase font-semibold hover:underline underline-offset-4
'
href={`/editArea/${uuid}`}
>
{areaName}
</Link>
<div className='flex items-center justify-between'>
<AreaIcon area={area} />
</div>
</div>
</div>
<Actions uuid={uuid} areaName={areaName} parentUuid={parentUuid} />
</div>
<div className='flex justify-end py-1'>
<Actions uuid={uuid} areaName={areaName} parentUuid={parentUuid} canDelete={canDelete} />
</div>
</div>
)
}

export const AreaIcon: React.FC<{ area: AreaType }> = ({ area: { climbs, metadata: { isBoulder } } }) => {
if ((climbs?.length ?? 0) > 0) {
return <EntityIcon type='crag' />
}
if (isBoulder) {
return <EntityIcon type='boulder' />
export const AreaIcon: React.FC<{ area: AreaType }> =
({ area: { climbs, metadata: { isBoulder } } }) => {
if ((climbs?.length ?? 0) > 0) {
return <EntityIcon type='crag' />
}
if (isBoulder) {
return <EntityIcon type='boulder' />
}
return <EntityIcon type='area' className='text-secondary' />
}
return <EntityIcon type='area' />
}

const Actions: React.FC<{ uuid: string, areaName: string, parentUuid: string }> = ({ uuid, parentUuid, areaName }) => {
const Actions: React.FC<{
uuid: string
areaName: string
parentUuid: string
canDelete?: boolean
}> = ({ uuid, parentUuid, areaName, canDelete = false }) => {
return (
<div className='flex items-center divide-x'>
<Link className='px-4' href={`/editArea/${uuid}`}>
<button className='btn btn-link btn-primary btn-sm'>Edit</button>
<button className='btn btn-link btn-primary btn-sm text-secondary'>Edit</button>
</Link>
<Link className='px-4' href={`/area/${uuid}`} target='_new'>
<button className='btn btn-link btn-primary btn-sm text-secondary'>View</button>
</Link>
<Link className='px-4' href={`/area/${uuid}`} target='_new'><button className='btn btn-link btn-primary btn-sm'>View</button></Link>
<span className='px-4'>
<div className='px-4 relative'>
<DeleteAreaTrigger
areaName={areaName}
areaUuid={uuid}
parentUuid={parentUuid}
returnToParentPageAfterDelete={false}
>
<DeleteAreaTriggerButtonSm disabled={false} />
<DeleteAreaTriggerButtonSm disabled={!canDelete} />
</DeleteAreaTrigger>
</span>
</div>
</div>
)
}
12 changes: 8 additions & 4 deletions src/app/editArea/[slug]/manage/components/AreaList.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,35 @@
'use client'
import { AreaType } from '@/js/types'
import { AreaItem } from './AreaItem'
import { GluttenFreeCrumbs } from '@/components/ui/BreadCrumbs'

type AreaListProps = Pick<AreaType, 'uuid' | 'areaName' | 'pathTokens' | 'ancestors'> & {
areas: AreaType[]
}

export const AreaList: React.FC<AreaListProps> = ({ areaName, uuid, pathTokens, ancestors, areas }) => {
return (
<div className='card card-compact card-bordered border-base-300/40 overflow-hidden w-full'>

<div className='card card-compact card-bordered border-base-300/50 overflow-hidden w-full'>
<div className='form-control'>
<div className='p-6'>
{/* Heading */}
<label className='flex flex-col items-start justify-start gap-2 pb-2'>
<div className='flex items-baseline gap-4'>
<h2 className='font-semibold text-2xl'>Child areas</h2>
<div className='text-secondary text-sm'>Total: <span>{areas.length}</span></div>
</div>
</label>

<GluttenFreeCrumbs ancestors={ancestors} pathTokens={pathTokens} />
<hr className='border mb-6' />

{/* Child area list */}
<div className='two-column-table'>
{areas.map((item, index) =>
<AreaItem key={item.uuid} area={item} index={index + 1} parentUuid={uuid} />)}
</div>
</div>
</div>
{/* <BreadCrumbs ancestors={ancestors} pathTokens={pathTokens} isClimbPage={false} /> */}

</div>
)
}
27 changes: 22 additions & 5 deletions src/app/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ h3 {
}

.text-primary {
@apply text-base-content;
@apply text-base-content !important;
}

.text-secondary {
@apply text-base-content/60;
@apply text-base-content/60 !important;
}

/* .card-body {
Expand Down Expand Up @@ -85,6 +85,23 @@ h3 {
@apply rounded h-8 w-8 grid place-content-center bg-area-cue text-base-100 text-sm hover:decoration-0 hover:no-underline;
}

.thick-link {
@apply hover:underline underline-offset-4 decoration-4;
}
@layer utilities {
.dialog-center {
@apply top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 rounded-box !important;
}

.dialog-default {
@apply top-0 left-0 h-screen md:dialog-center fixed z-50 w-screen max-w-screen-md bg-base-100 lg:drop-shadow-lg overflow-y-auto h-fit max-h-screen lg:max-h-[95vh];
}
.dialog-title {
@apply py-4 lg:py-5 left-0 top-0 w-full text-center fixed bg-base-100 bg-opacity-80 backdrop-blur-sm z-50 leading-none;
}

.dialog-close-button {
@apply fixed top-0.5 left-0.5 lg:top-1 lg:left-1 z-50;
}

.dialog-form-default {
@apply mt-16 lg:mt-20 px-2 lg:px-4 mb-6 !important;
}
}
3 changes: 2 additions & 1 deletion src/components/edit/DeleteAreaForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ interface HtmlFormProps {
* @param returnToParentPageAfterDelete true to be redirected to parent area page
* @param onSuccess Optional callback
*/
export default function DeleteAreaForm ({ areaUuid, areaName, parentUuid, returnToParentPageAfterDelete = true, onSuccess }: DeleteAreaProps): JSX.Element {
export default function DeleteAreaForm ({ areaUuid, areaName, parentUuid, returnToParentPageAfterDelete = false, onSuccess }: DeleteAreaProps): JSX.Element {
const session = useSession()
const router = useRouter()

Expand All @@ -39,6 +39,7 @@ export default function DeleteAreaForm ({ areaUuid, areaName, parentUuid, return
}, [session])

const onSuccessHandler = (): void => {
router.refresh()
if (onSuccess != null) {
onSuccess()
}
Expand Down
33 changes: 33 additions & 0 deletions src/components/ui/BreadCrumbs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,36 @@ const Item = ({ path, highlight, current, length }: ItemProps): JSX.Element => (
{sanitizeName(path)}
{current < length - 1 ? <span className='text-base-300 px-1'>{SEPARATOR}</span> : null}
</span>)

/**
* Area path crumbs based on DaisyUI.
*/
export const GluttenFreeCrumbs: React.FC<{
pathTokens: string[]
ancestors: string[]
}> = ({ pathTokens, ancestors }) => {
return (
<div className='breadcrumbs'>
<ul>
<li><a href='/' className='text-secondary'>Home</a></li>
{pathTokens.map((path, index) => {
const uuid = ancestors[index]
const url = `/editArea/${uuid}`
return <GFItem key={uuid} path={sanitizeName(path)} url={url} isLast={index === pathTokens.length - 1} />
})}
</ul>
</div>
)
}

const GFItem: React.FC<{ path: string, url: string, isLast: boolean }> =
({ path, url, isLast }) => (
<li>
<a
href={url}
className={clx(isLast ? 'text-primary pointer-events-none font-semibold' : 'text-secondary')}
>
{path}
</a>
</li>
)
3 changes: 3 additions & 0 deletions src/js/graphql/gql/areaById.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ export const QUERY_AREA_BY_ID = gql`
children {
uuid
}
climbs {
id
}
}
content {
description
Expand Down
4 changes: 3 additions & 1 deletion src/js/hooks/useUpdateAreasCmd.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,9 @@ export default function useUpdateAreasCmd ({ areaId, accessToken = '', ...props
MUTATION_REMOVE_AREA, {
client: graphqlClient,
onCompleted: (data) => {
void refreshPage(`/api/revalidate?s=${areaId}`) // rebuild parent area page
// void refreshPage(`/api/revalidate?s=${areaId}`) // rebuild parent area page

void updateAreaPageCache(areaId)

if (onDeleteCompleted != null) {
onDeleteCompleted(data)
Expand Down

0 comments on commit 8a91296

Please sign in to comment.