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

feat: Add bucket CRUD frontend #46

Merged
merged 40 commits into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
f67062c
Search input for buckets
Frens1999 Dec 1, 2023
8a38605
Merge branch 'main' into feature/fa-46-buckets-page
robinholzi Dec 1, 2023
f626a1f
Update code cov badges
actions-user Dec 1, 2023
6d0dcbe
Merge branch 'main' into feature/fa-46-buckets-page
Frens1999 Dec 4, 2023
9a02367
Merge branch 'feature/fa-46-buckets-page' of github.com:la-famiglia-j…
Frens1999 Dec 4, 2023
9de6460
Success or error popup
Frens1999 Dec 4, 2023
6b409b9
Go back button
Frens1999 Dec 4, 2023
b58379e
Get buckets from db
Frens1999 Dec 4, 2023
549ad54
Delete bucket modal
Frens1999 Dec 8, 2023
37b0daa
Edit bucket modal
Frens1999 Dec 8, 2023
e424027
Share bucket modal
Frens1999 Dec 9, 2023
c29d0c0
Merge branch 'main' into feature/fa-46-buckets-page
Frens1999 Dec 9, 2023
c02970a
Remove dummy data in bucket card
Frens1999 Dec 11, 2023
e9dbcac
Buckets page changes and remove Search Input component
Frens1999 Dec 11, 2023
6f61875
Fetch with auth token
Frens1999 Dec 11, 2023
79f3eba
Merge branch 'main' into feature/fa-46-buckets-page
Frens1999 Dec 11, 2023
5299676
Add bucket page
Frens1999 Dec 11, 2023
cda9c91
Bucket service to use the functions
Frens1999 Dec 11, 2023
cb115ec
Specific bucket page
Frens1999 Dec 11, 2023
6ebf290
Share bucket modal
Frens1999 Dec 11, 2023
bacdda9
Update code cov badges
actions-user Dec 11, 2023
61834ae
Remove the commented out code
Frens1999 Dec 12, 2023
59b7b67
Remove bucket type file
Frens1999 Dec 12, 2023
3ec1895
Merge branch 'main' into feature/fa-46-buckets-page
Frens1999 Dec 12, 2023
fca4ae3
Merge branch 'feature/fa-46-buckets-page' of github.com:la-famiglia-j…
Frens1999 Dec 12, 2023
231bff7
Update code cov badges
actions-user Dec 12, 2023
a0289c4
Requested changes
Frens1999 Dec 12, 2023
2ba148d
Update code cov badges
actions-user Dec 12, 2023
8025367
Pointer events none on dummy buckets
Frens1999 Dec 12, 2023
5e9719d
Merge branch 'main' into feature/fa-46-buckets-page
Frens1999 Dec 13, 2023
8943e3c
Remove setting the local storage
Frens1999 Dec 13, 2023
0fa2171
Minor changes for the add-bucket page
Frens1999 Dec 13, 2023
3d3fdf3
Change the logic of fetchWithAuth
Frens1999 Dec 13, 2023
813f2ac
Change PopupENUM to PopupType
Frens1999 Dec 13, 2023
9a8dede
Update code cov badges
actions-user Dec 13, 2023
e291e43
Show a message if bucketCompanies.length is 0
Frens1999 Dec 13, 2023
1fd0ce9
Merge branch 'main' into feature/fa-46-buckets-page
Frens1999 Dec 13, 2023
c2282ef
Update code cov badges
actions-user Dec 13, 2023
77ab9c7
Merge branch 'main' into feature/fa-46-buckets-page
robinholzi Dec 13, 2023
352c8af
Merge branch 'feature/fa-46-buckets-page' of github.com-rh:la-famigli…
robinholzi Dec 13, 2023
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
[![Deploy](https://github.com/la-famiglia-jst2324/parma-web/actions/workflows/release.yml/badge.svg?event=release)](https://parma.software)
[![Deploy](https://github.com/la-famiglia-jst2324/parma-web/actions/workflows/release.yml/badge.svg?event=push)](https://staging.parma.software)
[![Major Tag](https://github.com/la-famiglia-jst2324/parma-web/actions/workflows/tag-major.yml/badge.svg)](https://github.com/la-famiglia-jst2324/parma-web/actions/workflows/tag-major.yml)
![Functions](https://img.shields.io/badge/functions-21.63%25-red.svg?style=flat)
![Lines](https://img.shields.io/badge/lines-17.84%25-red.svg?style=flat)
![Functions](https://img.shields.io/badge/functions-17.75%25-red.svg?style=flat)
![Lines](https://img.shields.io/badge/lines-16.2%25-red.svg?style=flat)

ParmaAI webstack including frontend and REST API backend.

Expand Down
232 changes: 232 additions & 0 deletions src/app/buckets/[id]/page.tsx
Frens1999 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
'use client'

import { useEffect, useState } from 'react'
import type { Company, Bucket } from '@prisma/client'
import { Text, Button, Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow } from '@tremor/react'
import { useRouter } from 'next/navigation'
import { PencilIcon, ShareIcon, TrashIcon } from '@heroicons/react/20/solid'
import { GoBackButton } from '@/components/GoBackButton'
import EditBucketModal from '@/components/buckets/EditBucketModal'
import { Popup } from '@/components/Popup'
import { PopupType } from '@/types/popup'
import DeleteBucketModal from '@/components/buckets/DeleteBucketModal'
import BucketFunctions from '@/app/services/bucket.service'
import type { ShareBucketProps } from '@/components/buckets/ShareBucketModal'
import ShareBucketModal from '@/components/buckets/ShareBucketModal'
import { MainLayout } from '@/components/MainLayout'

const initialBucketValue = {
id: 0,
title: '',
description: '',
isPublic: true,
ownerId: 0,
createdAt: new Date(),
modifiedAt: new Date()
}

export default function BucketPage({ params: { id } }: { params: { id: string } }) {
Frens1999 marked this conversation as resolved.
Show resolved Hide resolved
const router = useRouter()
const [bucket, setBucket] = useState<Bucket>(initialBucketValue)
const [bucketCompanies, setBucketCompanies] = useState<Company[]>()
const [isShareModalOpen, setIsShareModalOpen] = useState(false)
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false)
const [isEditModalOpen, setIsEditModalOpen] = useState(false)
const [showSuccess, setShowSuccess] = useState(false)
const [showError, setShowError] = useState(false)
const [popupText, setPopupText] = useState('')

useEffect(() => {
BucketFunctions.getBucketById(+id)
.then((data) => {
setBucket(data)
BucketFunctions.getCompaniesForBucket(+id)
.then((res) => {
setBucketCompanies(res)
})
.catch((e) => {
console.log(e)
})
})
.catch((e) => {
console.log(e)
})
}, [id])

const toggleDeleteModal = () => {
setIsDeleteModalOpen((val) => !val)
}
const toggleEditModal = () => {
setIsEditModalOpen((val) => !val)
}
const toggleShareModal = () => {
setIsShareModalOpen((val) => !val)
}

const saveBucket = (title: string, description: string | null, isPublic: boolean) => {
BucketFunctions.updateBucket(title, description, +id, isPublic)
.then((res) => {
if (res) {
setBucket(res)
setShowSuccess(true)
setTimeout(() => setShowSuccess(false), 3000)
setPopupText('Bucket is updated successfully')
} else {
setShowError(true)
setTimeout(() => setShowError(false), 3000)
setPopupText('Failed to update bucket')
}
})
.catch((e) => {
setShowError(true)
setTimeout(() => setShowError(false), 3000)
setPopupText(e)
})
}

const onDeleteBucket = () => {
BucketFunctions.deleteBucket(+id)
.then((res) => {
if (res) {
setPopupText('Bucket is deleted successfully')
setShowSuccess(true)
setTimeout(() => {
setShowSuccess(false)
router.push('/buckets')
}, 1500)
}
})
.catch((error) => {
setPopupText(error)
setShowError(true)
setTimeout(() => setShowError(false), 3000)
})
setIsDeleteModalOpen(false)
}

const onHandleShare = (shareUsersList: ShareBucketProps[]) => {
shareUsersList.forEach((user) => {
BucketFunctions.shareBucket(user)
.then((res) => {
if (res) {
setPopupText('Bucket is shared successfully')
setShowSuccess(true)
setIsShareModalOpen(false)
setTimeout(() => {
setShowSuccess(false)
}, 3000)
}
})
.catch((e) => {
setPopupText(e)
setShowError(true)
setTimeout(() => {
setShowError(false)
setIsShareModalOpen(false)
}, 3000)
})
})
}
return (
<MainLayout>
<div className="mx-6 h-screen pt-12">
<div className="mx-auto rounded-lg border-0 bg-white p-6 shadow-md">
<div className="mb-4 flex items-center justify-between">
<div className="mb-3 flex items-start justify-start space-x-4">
<div className="mt-1">
<GoBackButton url="/buckets"></GoBackButton>
</div>
<div className="flex flex-col">
<h1 className="text-2xl font-bold">{bucket.title}</h1>
</div>
</div>
<div className="flex flex-row justify-evenly gap-2">
<Button
className="mr-2 flex items-center bg-transparent"
variant="secondary"
icon={ShareIcon}
onClick={toggleShareModal}
>
Share
</Button>
{isShareModalOpen && (
<ShareBucketModal
id={id}
handleShare={(shareUsersList: ShareBucketProps[]) => onHandleShare(shareUsersList)}
handleClose={toggleShareModal}
></ShareBucketModal>
)}
<Button
className="mr-2 flex items-center "
icon={PencilIcon}
variant="secondary"
color="gray"
onClick={toggleEditModal}
>
Edit Bucket
</Button>
{isEditModalOpen && (
<EditBucketModal
title={bucket.title}
description={bucket.description}
isPublic={bucket.isPublic}
handleClose={toggleEditModal}
handleSave={(title: string, description: string | null, isPublic: boolean) =>
saveBucket(title, description, isPublic)
}
></EditBucketModal>
)}
<Button
icon={TrashIcon}
variant="light"
color="red"
className="mr-2 flex items-center"
onClick={toggleDeleteModal}
>
Delete
</Button>
{isDeleteModalOpen && (
<DeleteBucketModal handleClose={toggleDeleteModal} handleDelete={onDeleteBucket}></DeleteBucketModal>
)}
</div>
</div>
<div className="mb-12 ml-8">
<p className="mb-4 text-gray-400">{bucket.description}</p>
</div>

<div className="ml-8 flex items-center justify-between">
<h1 className="text-2xl font-bold">Companies in this bucket</h1>
<div className="flex flex-row items-center gap-4"></div>
</div>

{bucketCompanies && bucketCompanies.length > 0 && (
<Table className="ml-8 mt-5">
<TableHead>
<TableRow>
<TableHeaderCell>Company Name</TableHeaderCell>
<TableHeaderCell>Description</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
{bucketCompanies?.map((item) => (
<TableRow key={item.name}>
<TableCell>{item.name}</TableCell>
<TableCell>
<Text className="whitespace-break-spaces">{item.description}</Text>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}

{bucketCompanies && !(bucketCompanies.length > 0) && (
<div className="ml-8 mt-4 text-gray-400">This bucket does not have any companies.</div>
)}
</div>
{showSuccess && <Popup text={popupText} title="Success" popupType={PopupType.SUCCESS}></Popup>}
{showError && <Popup text={popupText} title="Error" popupType={PopupType.ERROR}></Popup>}
</div>
</MainLayout>
)
}
155 changes: 155 additions & 0 deletions src/app/buckets/add-bucket/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
'use client'

import { Button, Switch, MultiSelect, MultiSelectItem } from '@tremor/react'
import type { FormEvent } from 'react'
import { useEffect, useState } from 'react'
import type { Bucket, Company } from '@prisma/client'
import { useRouter } from 'next/navigation'
import { CheckBadgeIcon } from '@heroicons/react/20/solid'
import { FormContent } from '@/components/FormContent'
import { GoBackButton } from '@/components/GoBackButton'
import { Popup } from '@/components/Popup'
import { PopupType } from '@/types/popup'
import BucketFunctions from '@/app/services/bucket.service'
import { MainLayout } from '@/components/MainLayout'

interface CompaniesPaginated {
companies: Company[]
pagination: {
currentPage: number
pageSize: number
totalPages: number
totalCount: number
}
}
export default function AddBucketPage() {
const [allCompaniesPaginated, setCompaniesPaginated] = useState<CompaniesPaginated>({
companies: [],
pagination: {
currentPage: 1,
pageSize: 10,
totalPages: 0,
totalCount: 0
}
})
// Bucket props
const [title, setTitle] = useState('')
const [description, setDescription] = useState('')
const [isPublic, setIsPublic] = useState(true)
const [selectedCompanies, setSelectedCompanies] = useState<string[]>([])
const [showSuccess, setShowSuccess] = useState(false)
const [showError, setShowError] = useState(false)
const router = useRouter()

const handleSwitchChange = (value: boolean) => {
setIsPublic(value)
}

useEffect(() => {
BucketFunctions.getAllCompanies()
.then(setCompaniesPaginated)
.catch((error) => {
console.error('Failed to fetch companies:', error)
})
}, [])

const createBucket = async (event: FormEvent<HTMLFormElement>) => {
event.preventDefault()
const formData = new FormData(event.currentTarget)
const bucket = {
title: formData.get('title') as string,
isPublic,
description: formData.get('description') as string
}

BucketFunctions.createBucket(bucket)
.then((data: Bucket) => {
// Add companies to the bucket
if (selectedCompanies.length > 0) {
BucketFunctions.addCompaniesToBucket(data.id, selectedCompanies)
.then((data) => console.log(data))
.catch((e) => console.log(e))
}
setShowSuccess(true)
setTimeout(() => {
router.push('/buckets')
setShowSuccess(false)
}, 1500) // Remove it from the screen
})
.catch((error) => {
setShowError(true)
setTimeout(() => setShowError(false), 1500) // Remove it from the screen
console.error('Error:', error)
})
}
return (
<MainLayout>
<div className="mx-6 h-screen pt-12">
<div className="mx-auto max-w-screen-xl rounded-lg border-0 bg-white p-6 shadow-md">
<div className="mb-3 flex items-start justify-start space-x-4">
<div className="mt-1">
<GoBackButton url="/buckets"></GoBackButton>
</div>
<div className="flex flex-col">
<h1 className="mb-2 text-2xl font-bold">Create Bucket</h1>
<p className="mb-4 text-gray-400">
You can create a collection of companies here. Please choose companies from a list of available
companies.
</p>
</div>
</div>
<form role="form" data-testid="create-bucket-form" className="px-8" onSubmit={createBucket}>
<div className="mb-8">
<FormContent
id="title"
name="title"
type="input"
value={title}
label="Bucket title"
placeholder="Please enter bucket title"
onChange={(e) => setTitle(e.target.value)}
/>
</div>
<div className="mb-8">
<FormContent
id="description"
name="description"
value={description}
label="Bucket description"
placeholder="Please enter bucket description"
onChange={(e) => setDescription(e.target.value)}
/>
</div>
<div className="mb-8">
<label className="mb-2 block text-sm font-bold text-gray-700">Companies</label>
<MultiSelect onValueChange={(e) => setSelectedCompanies(e || [])}>
{allCompaniesPaginated?.companies.map((company) => (
<MultiSelectItem key={company.id} value={`${company.id}`}>
{company.name}
</MultiSelectItem>
))}
</MultiSelect>
</div>

<div className="mb-8 flex items-center gap-4">
<div className="font-semibold">Make this bucket public</div>
<Switch id="switch" name="isPublic" checked={isPublic} onChange={handleSwitchChange} />
</div>
<div>
<Button>
<div className="flex items-center gap-2">
<CheckBadgeIcon className="h-5 w-5"></CheckBadgeIcon>
<div className="flex items-center gap-0.5 text-white">Create new Bucket</div>
</div>
</Button>
</div>
</form>
</div>
{showSuccess && (
<Popup text="Bucket created successfully" title="Success" popupType={PopupType.SUCCESS}></Popup>
)}
{showError && <Popup text="Bucket creation failed" title="Error" popupType={PopupType.ERROR}></Popup>}
</div>
</MainLayout>
)
}
Loading