Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/staging' into #386-Add-tags-comp…
Browse files Browse the repository at this point in the history
…onent
  • Loading branch information
Jasminamih committed Aug 11, 2024
2 parents f4523fb + 1cec487 commit a79368c
Show file tree
Hide file tree
Showing 12 changed files with 199 additions and 62 deletions.
8 changes: 8 additions & 0 deletions app/content-panel/blogs/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React from 'react';
import BlogListView from '../../../components/module-components/create-blogs/BlogListView';

const page = () => {
return <BlogListView />;
};

export default page;
49 changes: 4 additions & 45 deletions app/content-panel/page.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,7 @@
'use client';
import React from 'react';

import { useState } from 'react';
import Button from '../../components/reusable-components/button/Button';

const ContentPanel = () => {
const [activeTab, setActiveTab] = useState('blogs');

const handleTabClick = (tab: string) => {
setActiveTab(tab);
};

return (
<div>
<h1>Content Panel</h1>
<div>
<Button
href=""
type="button"
buttonClass={['primaryButton']}
buttonText="Blogs"
onClick={() => handleTabClick('blogs')}
/>
<Button
href=""
type="button"
buttonClass={['primaryButton']}
buttonText="Tags"
onClick={() => handleTabClick('tags')}
/>
</div>
<div>
{activeTab === 'blogs' && (
<div>
<h2>Blogs Section</h2>
</div>
)}
{activeTab === 'tags' && (
<div>
<h2>Tags Section</h2>
</div>
)}
</div>
</div>
);
const page = () => {
return <div />;
};

export default ContentPanel;
export default page;
6 changes: 3 additions & 3 deletions app/content-panel/tags/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
'use client';

import React from 'react';
import styles from './Tags.module.scss';
import AddTags from './AddTags';
import TagTable from './TagTable';
import styles from '../../../components/module-components/tags/Tags.module.scss';
import AddTags from '../../../components/module-components/tags/AddTags';
import TagTable from '../../../components/module-components/tags/TagTable';

const Tags = () => {
return (
Expand Down
115 changes: 115 additions & 0 deletions components/module-components/create-blogs/BlogListView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
'use client';

import React, { useEffect, useState } from 'react';
import Button from '../../reusable-components/button/Button';
import ReusableTable from '../../reusable-components/reusable-table/ReusableTable';
import Filter from '../SearchAndFilter/Filter';
import Search from '../SearchAndFilter/Search';
import ActionDropdown from '../../reusable-components/reusable-table/ActionDropdown';
import style from './createBlogs.module.scss';

interface Author {
first_name: string;
last_name: string;
}

interface Tag {
name: string;
}

interface BlogPostAPI {
id: string;
title: string;
tags: Tag[];
author: Author;
}

interface BlogPost {
id: string;
title: string;
tags: Tag[];
author: string;
}

const BlogListView = () => {
const [data, setData] = useState<BlogPost[]>([]);
const url = `${process.env.NEXT_PUBLIC_API_BASE_URL}/blog-posts`;

useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`${response.status} ${response.statusText}`);
}
const result = await response.json();
const transformedData: BlogPost[] = result.data.map((item: BlogPostAPI) => ({
id: item.id,
title: item.title,
tags: item.tags,
author: `${item.author.first_name} ${item.author.last_name}`,
}));
setData(transformedData);
} catch (error) {
console.error('Error fetching data:', error);
}
};

fetchData();
}, [url]);

const headers: (keyof BlogPost)[] = ['title', 'author', 'tags'];
const displayNames = {
title: 'Title',
author: 'Author',
tags: 'Tags',
};

const handleView = (id: string) => {
console.log('View blog', id);
};

const handleEdit = (id: string) => {
console.log('Edit blog', id);
};

const handleDelete = (id: string) => {
console.log('Delete blog', id);
};

const renderActionsDropdown = (item: BlogPost) => (
<ActionDropdown
dropdownItems={[
{ id: 'view', label: 'View', onClick: () => handleView(item.id) },
{ id: 'edit', label: 'Edit', onClick: () => handleEdit(item.id) },
{ id: 'delete', label: 'Delete', onClick: () => handleDelete(item.id) },
]}
/>
);

return (
<div className={style.mainContainer}>
<div className={style.inputWrapper}>
<Search handleInputChange={() => {}} searchValue="Search" />
<div className={style.rightContainer}>
<Filter handleRoleChange={() => {}} />
<Button
href=""
type="button"
buttonText="Add Blog"
buttonClass={['primaryButton']}
moveIcon
/>
</div>
</div>
<ReusableTable
headers={headers}
displayNames={displayNames}
data={data}
renderActionsDropdown={renderActionsDropdown}
/>
</div>
);
};

export default BlogListView;
14 changes: 14 additions & 0 deletions components/module-components/create-blogs/createBlogs.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.mainContainer {
margin: 50px 0;

.inputWrapper {
margin: 50px 0;
display: flex;
width: 100%;

.rightContainer {
display: flex;
gap: 20px;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useState } from 'react';
import Button from '../../../components/reusable-components/button/Button';
import Input from '../../../components/reusable-components/input/Input';
import styles from './addTags.module.scss';
import Input from '../../reusable-components/input/Input';
import Button from '../../reusable-components/button/Button';

const AddTags = () => {
const [searchTerm, setSearchTerm] = useState('');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use client';

import React, { useState } from 'react';
import ReusableTable from '../../../components/reusable-components/reusable-table/ReusableTable';
import Button from '../../../components/reusable-components/button/Button';
import Button from '../../reusable-components/button/Button';
import ReusableTable from '../../reusable-components/reusable-table/ReusableTable';

interface Tag {
id: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@import '../../styles/utils/breakpoints';
@import '../../../app/styles/utils/breakpoints';

.container {
width: 100%;
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import style from './actionDropdown.module.scss';
interface DropdownItem {
id: string;
label: string;
onClick: () => void;
}
interface ActionDropdownProps {
dropdownItems: DropdownItem[];
Expand Down
33 changes: 26 additions & 7 deletions components/reusable-components/reusable-table/ReusableTable.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
'use client';
/* eslint-disable no-unused-vars */

import React, { useState, useMemo } from 'react';
import TableRowComponent from './TableRowComponent';
Expand All @@ -10,7 +10,7 @@ interface ReusableTableProps<T> {
displayNames: { [key in keyof T]?: string };
data: T[];
renderActions?: (item: T) => React.ReactNode;
renderActionsDropdown?: React.ReactNode;
renderActionsDropdown?: (item: T) => React.ReactNode;
}

interface SortState<T> {
Expand All @@ -26,6 +26,11 @@ const ReusableTable = <T extends { id: string }>({
renderActionsDropdown,
}: ReusableTableProps<T>): React.JSX.Element => {
const [sortState, setSortState] = useState<SortState<T>[]>([]);
const [checkedId, setCheckedId] = useState<string | null>(null);

const handleCheckboxChange = (id: string) => {
setCheckedId(id === checkedId ? null : id);
};

const handleSort = (field: keyof T) => {
setSortState((prevSortState) => {
Expand All @@ -35,14 +40,25 @@ const ReusableTable = <T extends { id: string }>({
});
};

const getNestedValue = (obj: any, path: string) => {
return path.split('.').reduce((acc, part) => acc && acc[part], obj);
};

const sortedData = useMemo(() => {
if (sortState.length > 0) {
return [...data].sort((a, b) => {
const sort = sortState[0];
if (a[sort.field] > b[sort.field]) {
const aValue = getNestedValue(a, sort.field as string);
const bValue = getNestedValue(b, sort.field as string);

if (typeof aValue === 'string' && typeof bValue === 'string') {
return sort.order === 'asc' ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue);
}

if (aValue > bValue) {
return sort.order === 'asc' ? 1 : -1;
}
if (a[sort.field] < b[sort.field]) {
if (aValue < bValue) {
return sort.order === 'asc' ? -1 : 1;
}
return 0;
Expand All @@ -58,11 +74,11 @@ const ReusableTable = <T extends { id: string }>({
return (
<div className={style.tableWrapper}>
<table className={style.reusableTable}>
<TableHead<T>
<TableHead
headers={headers}
sortState={sortState}
onSort={handleSort}
displayNames={displayNames}
displayNames={displayNames as { [key in keyof T]?: string }}
showActions={!!renderActions}
showDropdownActions={!!renderActionsDropdown}
/>
Expand All @@ -71,9 +87,12 @@ const ReusableTable = <T extends { id: string }>({
<TableRowComponent<T>
key={item.id}
data={item}
isChecked={checkedId === item.id}
onCheckboxChange={handleCheckboxChange}
displayFields={headers}
showCheckbox={false}
renderActions={renderActions}
renderActionsDropdown={renderActionsDropdown}
renderActionsDropdown={renderActionsDropdown && renderActionsDropdown(item)}
/>
))}
</tbody>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,44 @@
/* eslint-disable no-unused-vars */

import React, { ReactNode } from 'react';
import style from './tableRowComponent.module.scss';

interface TableRowComponentProps<T> {
interface TableRowComponentProps<T extends { id: string }> {
data: T;
displayFields: (keyof T)[];
isChecked?: boolean;
onCheckboxChange: (id: string) => void;
showCheckbox?: boolean;
renderActions?: (item: T) => React.ReactNode;
renderActionsDropdown?: ReactNode;
}

const TableRowComponent = <T extends { id: string }>({
data,
displayFields,
isChecked,
onCheckboxChange,
showCheckbox,
renderActions,
renderActionsDropdown,
}: TableRowComponentProps<T>) => {
const handleCheckboxChange = () => {
onCheckboxChange(data.id);
};

return (
<tr className={style.rowComponent}>
{showCheckbox && (
<td aria-label="Checkbox">
<input type="checkbox" checked={isChecked} onChange={handleCheckboxChange} />
</td>
)}
{displayFields.map((field) => (
<td key={field as string}>{String(data[field])}</td>
<td key={field as string}>
{Array.isArray(data[field])
? (data[field] as unknown as { name: string }[]).map((item) => item.name).join(', ')
: String(data[field])}
</td>
))}

{renderActionsDropdown && (
Expand Down

0 comments on commit a79368c

Please sign in to comment.