Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ODP-160]: Dashboard functionality implementation #84

Merged
merged 26 commits into from
Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion deployment/frontend/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ await import("./src/env.mjs");

/** @type {import("next").NextConfig} */
const config = {
eslint: {
eslint: {
// Warning: This allows production builds to successfully complete even if
// your project has ESLint errors.
ignoreDuringBuilds: true,
Expand All @@ -22,6 +22,9 @@ const config = {
locales: ["en"],
defaultLocale: "en",
},
images: {
domains: ['wri.dev.ckan.datopian.com', 'test-bucket-wri.s3.ap-northeast-1.amazonaws.com', 'ckan-dev'],
},
};

export default config;
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,22 @@ export interface activity {
}

export default function ActivityStreamCard({ activity }: { activity: activity }) {
const iconMap: Record<string, string> = {
changed: "edit",
new: "add",
deleted: "update",
}
return (
<div className='w-full flex gap-x-3'>
<div className='bg-white p-2 flex justify-center items-center rounded-full w-fit h-fit mt-2 shadow'>
<div className='relative w-[20px] h-[18px] sm:w-[16px] sm:y-[16px] '>
<Image src={`/icons/${activity.icon}.svg`} alt="update" fill />
<Image src={`/icons/${iconMap[activity.icon]}.svg`} alt="update" fill />
</div>
</div>

<div className='flex flex-col'>
<p className=' line-clamp-1 font-normal text-base'>someone updated the dataset XYZ Lorem ipsum this will probably be longer</p>
<span className='font-normal text-xs text-wri-dark-gray'>1 hour ago</span>
<p className=' line-clamp-1 font-normal text-base'>{activity.description}</p>
<span className='font-normal text-xs text-wri-dark-gray'>{activity.time}</span>
</div>
</div>
)
Expand Down
21 changes: 15 additions & 6 deletions deployment/frontend/src/components/dashboard/ActivityStreams.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import ActivityStreamCard from '../_shared/ActivityStreamCard'
import type { activity } from '../_shared/ActivityStreamCard'
import { activity as activitydata } from '../_shared/ActivityStreamList'
import { Squares2X2Icon } from '@heroicons/react/24/outline'
import { api } from '@/utils/api';
import Spinner from '@/components/_shared/Spinner';
import Link from 'next/link'


Expand All @@ -15,8 +17,10 @@ function ActivityStreamUser({ activity }: { activity: activity }) {
)
}
export default function UserActivityStreams({ drag }: { drag: boolean }) {
const { data, isLoading } = api.dashboardActivity.listActivityStreamDashboard.useQuery({ search: '', page: { start: 0, rows: 100 } });

return (
<section id="favourites" className={`p-6 w-full shadow-wri h-full overflow-y-auto ${drag ? "border-dashed border border-wri-black " : ""}`}>
<section id="activities" className={`p-6 w-full shadow-wri h-full overflow-y-auto ${drag ? "border-dashed border border-wri-black " : ""}`}>
{
drag ? (
<div className='absolute top-0 -left-5'>
Expand All @@ -29,11 +33,16 @@ export default function UserActivityStreams({ drag }: { drag: boolean }) {
<Link href="/dashboard/activity-stream" className='ml-auto flex items-center font-semibold gap-x-1 text-[14px] text-wri-green'><span>See all</span> <ArrowRightIcon className='w-4 h-4 mb-1' /></Link>
</div>
{
activitydata.slice(0, 6).map((items, index) => {
return (
<ActivityStreamUser key={index} activity={items as activity} />
)
})
isLoading ? (
<div className='flex justify-center items-center h-full'>
<Spinner className='' />
</div>
) :
data?.activity.slice(0, 6).map((items, index) => {
return (
<ActivityStreamUser key={index} activity={items as activity} />
)
})
}
</section>

Expand Down
36 changes: 29 additions & 7 deletions deployment/frontend/src/components/dashboard/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,19 @@ import Favourites from "./Favourites";
import Notifications from "./Notifications";
import UserActivityStreams from "./ActivityStreams";
import { Cog8ToothIcon, CheckIcon } from "@heroicons/react/24/outline";

import { api } from '@/utils/api';


export default function Dashboard() {

const [state, setState] = useState([
{ id: 1, name: QuickAction },
{ id: 2, name: Favourites },
{ id: 3, name: Notifications },
{ id: 4, name: UserActivityStreams }
{ id: 1, name: "quickAction" },
{ id: 2, name: "favourites" },
{ id: 3, name: "notifications" },
{ id: 4, name: "userActivityStreams" }
]);


const [drag, setDrag] = useState({
sortable: false,
draggable: "false"
Expand Down Expand Up @@ -54,13 +56,17 @@ export default function Dashboard() {

<ReactSortable list={state} setList={setState} className={`h-full grid grid-cols-1 sm:grid-cols-2 sm:grid-rows-2 gap-6 xxl:gap-6 ${drag.sortable ? "grid" : "hidden"}`} sort={true} style={{ zIndex: 9999 }}>
{state.map((item) => (
<div key={item.id} className=" w-full h-[463px] relative">{(<item.name drag={true} />)}</div>
<div key={item.id} className=" w-full h-[463px] relative">
<SelectComponent name={item.name} drag={true} key={item.name} />
</div>
))}
</ReactSortable>

<div className={`h-full grid grid-cols-1 sm:grid-cols-2 sm:grid-rows-2 gap-6 xxl:gap-6 ${drag.sortable ? "hidden" : "grid"}`} >
{state.map((item) => (
<div key={item.id} className=" w-full h-[463px] relative">{(<item.name drag={false} />)}</div>
<div key={item.id} className=" w-full h-[463px] relative">
<SelectComponent name={item.name} drag={false} />
</div>
))}
</div>

Expand All @@ -69,3 +75,19 @@ export default function Dashboard() {

)
}


function SelectComponent({ name, drag }: { name: string, drag: boolean }) {
switch (name) {
case "quickAction":
return <QuickAction drag={drag} />
case "favourites":
return <Favourites drag={drag} />
case "notifications":
return <Notifications drag={drag} />
case "userActivityStreams":
return <UserActivityStreams drag={drag} />
default:
return <QuickAction drag={drag} />
}
}
27 changes: 17 additions & 10 deletions deployment/frontend/src/components/dashboard/Favourites.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
import React from 'react'
import React, { useState } from 'react'
import { ArrowPathIcon, ArrowRightIcon, Squares2X2Icon } from '@heroicons/react/24/outline'
import Link from 'next/link'
import { api } from '@/utils/api'
import type { SearchInput } from '@/schema/search.schema';
import type { WriDataset } from '@/schema/ckan.schema';
import { formatDate } from '@/utils/general';


function Favourite() {
function Favourite({ dataset }: { dataset: WriDataset }) {
const created = dataset?.metadata_created ? dataset.metadata_created : ''
return (
<div className='flex flex-col hover:bg-slate-100 px-3 py-2 mb-2 pb-2 rounded-md'>
<p className='font-normal text-base'>De Finibus Bonorum et Malorum, written by Cicero in 45 BC</p>
<p className='font-normal text-base'>{dataset?.title ?? dataset?.name}</p>
<div className='flex '>
<ArrowPathIcon className='w-3 h-3 text-[#3654A5] mt-[2px]' />
<div className='ml-1 w-fit h-[12px] text-[12px]'>
20 Sept 2022
{formatDate(created)}
</div>
</div>
</div>
)
}
export default function Favourites({ drag }: { drag: boolean }) {
const [query, setQuery] = useState<SearchInput>({ search: '', page: { start: 0, rows: 7 } })
const { data, isLoading } = api.dataset.getFavoriteDataset.useQuery(query)
return (
<section id="favourites" className={`p-6 w-full shadow-wri h-full overflow-y-auto ${drag ? "border-dashed border border-wri-black" : ""}`}>
{
Expand All @@ -31,11 +37,12 @@ export default function Favourites({ drag }: { drag: boolean }) {
<Link href="/dashboard/datasets" className='ml-auto flex items-center font-semibold gap-x-1 text-[14px] text-wri-green'><span>See all</span> <ArrowRightIcon className='w-4 h-4 mb-1' /></Link>
</div>
{
[1, 2, 3, 4, 5, 6].map((items) => {
return (
<Favourite key={items} />
)
})
data?.datasets.length === 0 ? <div className='flex justify-center items-center h-screen'>No data</div> :
data?.datasets.map((items, index) => {
return (
<Favourite key={index} dataset={items} />
)
})
}
</section>

Expand Down
17 changes: 13 additions & 4 deletions deployment/frontend/src/components/dashboard/UserProfile.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
import React from 'react'
import Image from 'next/image'
import { api } from '@/utils/api';
import Spinner from '../_shared/Spinner';

export default function UserProfile() {
const { data, isLoading } = api.user.getDashboardUser.useQuery();

if (isLoading) return (
<div className='w-full flex flex-col justify-center items-center font-acumin gap-y-2 text-white pb-6 pt-10'>
<Spinner />
</div>
)
return (
<div className='w-full flex flex-col justify-center items-center font-acumin gap-y-2 text-white pb-6 pt-10'>
<div className='relative w-24 h-24 rounded-full overflow-hidden'>
<Image src='/images/placeholders/user/dummypro.png' fill alt='' />
</div>
<div className='text-[1.438rem] leading-[1.725rem] font-semibold '>John Doe</div>
<div className='font-normal text-base '>System Admin</div>
<div className='text-[1.438rem] leading-[1.725rem] font-semibold '>{data?.userdetails?.name}</div>
<div className='font-normal text-base '>{data?.userdetails?.isSysAdmin ? "System Admin" : "Member"}</div>
<div className=' text-base font-light'>
<span className=' mr-2'>2 Teams</span>
<span className=' mr-2'>{data?.userdetails?.teamCount} Teams</span>
<span className='w-0 h-4 border'></span>
<span className='ml-2'>0 Datasets</span>
<span className='ml-2'>{data?.userdetails?.datasetCount} Datasets</span>
</div>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,38 @@
import React from 'react'
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/24/outline'
import type { SearchInput } from '@/schema/search.schema'
import Spinner from '@/components/_shared/Spinner'

export default function Pagination({ setQuery, query, isLoading, count }: {
setQuery: React.Dispatch<React.SetStateAction<SearchInput>>,
query: SearchInput,
isLoading?: boolean,
count?: number
}) {

const handlePageChange = (page: number) => {

if (setQuery && query && (page >= 0 && page < count!)) {
const updateQuery: SearchInput = { page: { ...query.page, start: page }, search: query.search }
setQuery(updateQuery)
}
}

export default function Pagination() {
return (
<div className='flex font-acumin gap-x-2 items-center self-end'>
<div className=' text-gray-300 font-light text-sm'>1-50 of 29,097</div>
<div>
{isLoading ? <Spinner className="mx-auto my-2" /> :
<div className=' text-gray-300 font-light text-sm'>{`${query.page.start}-${query.page.rows > count! ? count : (query.page.start + query.page.rows)} of ${count}`}</div>}

<button
onClick={() => handlePageChange(query.page.start - query.page.rows)}
>
<ChevronLeftIcon className='w-5 h-5 text-wri-gray' />
</div>
<div>
</button>
<button
onClick={() => handlePageChange(query.page.start + query.page.rows)}
>
<ChevronRightIcon className='w-5 h-5 text-wri-black' />
</div>
</button>
</div>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export default function Row({ rowMain, rowSub, isDropDown, controlButtons, linkB
}) : ""}

{(isDropDown) ? (
<button className={`flex items-center gap-x-2 px-2 py-1 rounded-md`}
<button id="rowshow" className={`flex items-center gap-x-2 px-2 py-1 rounded-md`}
onClick={() => setIsShowSubRow(!isShowSubRow)}
>
{isShowSubRow ? (<ChevronUpIcon className=' w-4 h-4 text-black' />) : (<ChevronDownIcon className=' w-4 h-4 text-black' />)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export interface IRowProfile {
title?: string;
description?: string;
image?: string;
image_display_url?: string;
name?: string;
}


Expand All @@ -14,11 +16,11 @@ export default function RowProfile({ imgStyle, isPad, profile }: { imgStyle?: st
<div className='flex flex-row gap-x-4 hover:bg-slate-100 rounded-md'>
<div className='flex gap-x-4'>
<div className={`relative ${imgStyle ? imgStyle : "w-10 h-10"} `}>
<Image src={profile?.image ? profile.image : '/images/placeholders/user/userpics.png'} alt='' className='rounded-md' fill />
<Image src={profile?.image_display_url ? profile.image_display_url : '/images/placeholders/user/userpics.png'} alt='' className='rounded-md' fill />
</div>
</div>
<div className={`flex flex-col ${isPad ? "py-3" : ''}`}>
<p className='font-normal text-base'>{profile?.title ? profile.title : "sent you a request for approval"}</p>
<p className='font-normal text-base'>{profile?.title || profile?.name ? profile.title || profile.name : "sent you a request for approval"}</p>
{profile?.description ? (<span className='text-[#666666] font-tight text-[12px] '>{profile.description}</span>) : ""}
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,63 @@
import React from 'react'
import React, { useRef } from 'react'
import TableHeader from './TableHeader'
import { MagnifyingGlassIcon } from "@heroicons/react/20/solid";
import type { SearchInput } from '@/schema/search.schema';

function LeftNode({ placeholder, setQuery, query }:
{
placeholder?: string,
setQuery: React.Dispatch<React.SetStateAction<SearchInput>>,
query: SearchInput
}) {

const inputRef = useRef<HTMLInputElement>(null)

const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
if (inputRef.current) {
const updateQuery: SearchInput = { page: { ...query.page, start: 0 }, search: inputRef.current.value }
setQuery && setQuery(updateQuery)
}
}

function LeftNode({ placeholder }: { placeholder?: string }) {
return (
<div className=' px-2 py-4 gap-x-2 flex flex-row items-center min-w-fit w-full bg-white'>
<div className='grow shrink basis-auto'><input type="text" placeholder={`${placeholder ? placeholder : "Search by keywords"}`} className=' focus:outline-none outline-none border-none focus:border-none focus:ring-0 focus:ring-offset-0 placeholder:text-[14px] text-[14px] font-light w-full' /></div>
<div className=' my-auto'><MagnifyingGlassIcon className='w-4 h-4 text-wri-black' /></div>
</div>
<form onSubmit={(e) => handleSubmit(e)} className='w-full'>
<div className=' px-2 py-4 gap-x-2 flex flex-row items-center min-w-fit w-full bg-white'>
<div className='grow shrink basis-auto'><input type="search" ref={inputRef} placeholder={`${placeholder ? placeholder : "Search by keywords"}`} className=' focus:outline-none outline-none border-none focus:border-none focus:ring-0 focus:ring-offset-0 placeholder:text-[14px] text-[14px] font-light w-full' /></div>
<button
type='submit'
className=' my-auto'>
<MagnifyingGlassIcon className='w-4 h-4 text-wri-black' />
</button>
</div>
</form>
)
}

export default function SearchHeader(
{ RightNode,
rightStyle,
leftStyle,
placeholder }:
{ RightNode?: React.ReactNode, rightStyle?: string, leftStyle?: string, placeholder?: string }) {
setQuery,
query,
placeholder,
Pagination
}:
{
RightNode?: React.ReactNode,
rightStyle?: string,
leftStyle?: string,
placeholder?: string,
setQuery: React.Dispatch<React.SetStateAction<SearchInput>>,
query: SearchInput,
Pagination: React.ReactNode
}) {
return (
<TableHeader rightNode={RightNode} leftNode={<LeftNode placeholder={placeholder} />} rightStyle={rightStyle} leftstyle={leftStyle} />
<TableHeader
rightNode={RightNode}
leftNode={<LeftNode placeholder={placeholder} setQuery={setQuery} query={query} />}
rightStyle={rightStyle} leftstyle={leftStyle}
Pagination={Pagination}
/>
)
}
Loading
Loading