v1
This commit is contained in:
parent
5aa8382fbf
commit
a07e0b335c
37
backend/src/db/models/project.js
Normal file
37
backend/src/db/models/project.js
Normal file
@ -0,0 +1,37 @@
|
||||
const { DataTypes } = require('sequelize');
|
||||
|
||||
module.exports = (sequelize) => {
|
||||
const Project = sequelize.define(
|
||||
'project',
|
||||
{
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: false,
|
||||
validate: {
|
||||
len: [2, 255],
|
||||
notEmpty: true,
|
||||
},
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
paranoid: true,
|
||||
},
|
||||
);
|
||||
|
||||
Project.associate = (models) => {
|
||||
models.project.hasMany(models.task, {
|
||||
as: 'tasks',
|
||||
});
|
||||
};
|
||||
|
||||
return Project;
|
||||
};
|
||||
48
backend/src/db/models/task.js
Normal file
48
backend/src/db/models/task.js
Normal file
@ -0,0 +1,48 @@
|
||||
const { DataTypes } = require('sequelize');
|
||||
|
||||
module.exports = (sequelize) => {
|
||||
const Task = sequelize.define(
|
||||
'task',
|
||||
{
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
},
|
||||
title: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: false,
|
||||
validate: {
|
||||
notEmpty: true,
|
||||
},
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM,
|
||||
values: ['todo', 'in_progress', 'done'],
|
||||
defaultValue: 'todo',
|
||||
},
|
||||
deadline: {
|
||||
type: DataTypes.DATE,
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
paranoid: true,
|
||||
},
|
||||
);
|
||||
|
||||
Task.associate = (models) => {
|
||||
models.task.belongsTo(models.project, {
|
||||
as: 'project',
|
||||
});
|
||||
|
||||
models.task.belongsTo(models.users, {
|
||||
as: 'assignee',
|
||||
});
|
||||
};
|
||||
|
||||
return Task;
|
||||
};
|
||||
@ -254,7 +254,8 @@ router.post(
|
||||
const response = await LocalAIApi.createResponse(payload, options);
|
||||
|
||||
if (response.success) {
|
||||
return res.status(200).send(response);
|
||||
const text = LocalAIApi.extractText(response);
|
||||
return res.status(200).send({ text });
|
||||
}
|
||||
|
||||
console.error('AI proxy error:', response);
|
||||
|
||||
@ -1,40 +1,40 @@
|
||||
import type { ColorButtonKey } from './interfaces'
|
||||
|
||||
export const gradientBgBase = 'bg-gradient-to-tr'
|
||||
export const colorBgBase = "bg-violet-50/50"
|
||||
export const gradientBgPurplePink = `${gradientBgBase} from-purple-400 via-pink-500 to-red-500`
|
||||
export const gradientBgViolet = `${gradientBgBase} ${colorBgBase}`
|
||||
export const gradientBgDark = `${gradientBgBase} from-dark-700 via-dark-900 to-dark-800`;
|
||||
export const gradientBgPinkRed = `${gradientBgBase} from-pink-400 via-red-500 to-yellow-500`
|
||||
export const gradientBgBase = 'bg-gray-100 dark:bg-dark-900'
|
||||
export const colorBgBase = "bg-gray-100 dark:bg-dark-900"
|
||||
export const gradientBgPurplePink = `bg-gray-100 dark:bg-dark-900`
|
||||
export const gradientBgViolet = `bg-gray-100 dark:bg-dark-900`
|
||||
export const gradientBgDark = `bg-dark-900`;
|
||||
export const gradientBgPinkRed = `bg-gray-100 dark:bg-dark-900`
|
||||
|
||||
export const colorsBgLight = {
|
||||
white: 'bg-white text-black',
|
||||
light: ' bg-white text-black text-black dark:bg-dark-900 dark:text-white',
|
||||
contrast: 'bg-gray-800 text-white dark:bg-white dark:text-black',
|
||||
success: 'bg-emerald-500 border-emerald-500 dark:bg-pavitra-blue dark:border-pavitra-blue text-white',
|
||||
danger: 'bg-red-500 border-red-500 text-white',
|
||||
warning: 'bg-yellow-500 border-yellow-500 text-white',
|
||||
info: 'bg-blue-500 border-blue-500 dark:bg-pavitra-blue dark:border-pavitra-blue text-white',
|
||||
success: 'bg-gray-500 border-gray-500 dark:bg-gray-700 dark:border-gray-700 text-white',
|
||||
danger: 'bg-gray-500 border-gray-500 text-white',
|
||||
warning: 'bg-gray-500 border-gray-500 text-white',
|
||||
info: 'bg-gray-500 border-gray-500 dark:bg-gray-700 dark:border-gray-700 text-white',
|
||||
}
|
||||
|
||||
export const colorsText = {
|
||||
white: 'text-black dark:text-slate-100',
|
||||
light: 'text-gray-700 dark:text-slate-400',
|
||||
contrast: 'dark:text-white',
|
||||
success: 'text-emerald-500',
|
||||
danger: 'text-red-500',
|
||||
warning: 'text-yellow-500',
|
||||
info: 'text-blue-500',
|
||||
success: 'text-gray-500',
|
||||
danger: 'text-gray-500',
|
||||
warning: 'text-gray-500',
|
||||
info: 'text-gray-500',
|
||||
};
|
||||
|
||||
export const colorsOutline = {
|
||||
white: [colorsText.white, 'border-gray-100'].join(' '),
|
||||
light: [colorsText.light, 'border-gray-100'].join(' '),
|
||||
contrast: [colorsText.contrast, 'border-gray-900 dark:border-slate-100'].join(' '),
|
||||
success: [colorsText.success, 'border-emerald-500'].join(' '),
|
||||
danger: [colorsText.danger, 'border-red-500'].join(' '),
|
||||
warning: [colorsText.warning, 'border-yellow-500'].join(' '),
|
||||
info: [colorsText.info, 'border-blue-500'].join(' '),
|
||||
success: [colorsText.success, 'border-gray-500'].join(' '),
|
||||
danger: [colorsText.danger, 'border-gray-500'].join(' '),
|
||||
warning: [colorsText.warning, 'border-gray-500'].join(' '),
|
||||
info: [colorsText.info, 'border-gray-500'].join(' '),
|
||||
};
|
||||
|
||||
export const getButtonColor = (
|
||||
@ -53,30 +53,30 @@ export const getButtonColor = (
|
||||
whiteDark: 'ring-gray-200 dark:ring-dark-500',
|
||||
lightDark: 'ring-gray-200 dark:ring-gray-500',
|
||||
contrast: 'ring-gray-300 dark:ring-gray-400',
|
||||
success: 'ring-emerald-300 dark:ring-pavitra-blue',
|
||||
danger: 'ring-red-300 dark:ring-red-700',
|
||||
warning: 'ring-yellow-300 dark:ring-yellow-700',
|
||||
info: "ring-blue-300 dark:ring-pavitra-blue",
|
||||
success: 'ring-gray-300 dark:ring-gray-700',
|
||||
danger: 'ring-gray-300 dark:ring-gray-700',
|
||||
warning: 'ring-gray-300 dark:ring-gray-700',
|
||||
info: "ring-gray-300 dark:ring-gray-700",
|
||||
},
|
||||
active: {
|
||||
white: 'bg-gray-100',
|
||||
whiteDark: 'bg-gray-100 dark:bg-dark-800',
|
||||
lightDark: 'bg-gray-200 dark:bg-slate-700',
|
||||
contrast: 'bg-gray-700 dark:bg-slate-100',
|
||||
success: 'bg-emerald-700 dark:bg-pavitra-blue',
|
||||
danger: 'bg-red-700 dark:bg-red-600',
|
||||
warning: 'bg-yellow-700 dark:bg-yellow-600',
|
||||
info: 'bg-blue-700 dark:bg-pavitra-blue',
|
||||
success: 'bg-gray-700 dark:bg-gray-800',
|
||||
danger: 'bg-gray-700 dark:bg-gray-600',
|
||||
warning: 'bg-gray-700 dark:bg-gray-600',
|
||||
info: 'bg-gray-700 dark:bg-gray-800',
|
||||
},
|
||||
bg: {
|
||||
white: 'bg-white text-black',
|
||||
whiteDark: 'bg-white text-black dark:bg-dark-900 dark:text-white',
|
||||
lightDark: 'bg-gray-100 text-black dark:bg-slate-800 dark:text-white',
|
||||
contrast: 'bg-gray-800 text-white dark:bg-white dark:text-black',
|
||||
success: 'bg-emerald-600 dark:bg-pavitra-blue text-white',
|
||||
danger: 'bg-red-600 text-white dark:bg-red-500 ',
|
||||
warning: 'bg-yellow-600 dark:bg-yellow-500 text-white',
|
||||
info: " bg-blue-600 dark:bg-pavitra-blue text-white ",
|
||||
success: 'bg-gray-600 dark:bg-gray-700 text-white',
|
||||
danger: 'bg-gray-600 text-white dark:bg-gray-500 ',
|
||||
warning: 'bg-gray-600 dark:bg-gray-500 text-white',
|
||||
info: " bg-gray-600 dark:bg-gray-700 text-white ",
|
||||
},
|
||||
bgHover: {
|
||||
white: 'hover:bg-gray-100',
|
||||
@ -84,39 +84,39 @@ export const getButtonColor = (
|
||||
lightDark: 'hover:bg-gray-200 hover:dark:bg-slate-700',
|
||||
contrast: 'hover:bg-gray-700 hover:dark:bg-slate-100',
|
||||
success:
|
||||
'hover:bg-emerald-700 hover:border-emerald-700 hover:dark:bg-pavitra-blue hover:dark:border-pavitra-blue',
|
||||
'hover:bg-gray-700 hover:border-gray-700 hover:dark:bg-gray-800 hover:dark:border-gray-800',
|
||||
danger:
|
||||
'hover:bg-red-700 hover:border-red-700 hover:dark:bg-red-600 hover:dark:border-red-600',
|
||||
'hover:bg-gray-700 hover:border-gray-700 hover:dark:bg-gray-600 hover:dark:border-gray-600',
|
||||
warning:
|
||||
'hover:bg-yellow-700 hover:border-yellow-700 hover:dark:bg-yellow-600 hover:dark:border-yellow-600',
|
||||
info: "hover:bg-blue-700 hover:border-blue-700 hover:dark:bg-pavitra-blue/80 hover:dark:border-pavitra-blue/80",
|
||||
'hover:bg-gray-700 hover:border-gray-700 hover:dark:bg-gray-600 hover:dark:border-gray-600',
|
||||
info: "hover:bg-gray-700 hover:border-gray-700 hover:dark:bg-gray-800/80 hover:dark:border-gray-800/80",
|
||||
},
|
||||
borders: {
|
||||
white: 'border-white',
|
||||
whiteDark: 'border-white dark:border-dark-900',
|
||||
lightDark: 'border-gray-100 dark:border-slate-800',
|
||||
contrast: 'border-gray-800 dark:border-white',
|
||||
success: 'border-emerald-600 dark:border-pavitra-blue',
|
||||
danger: 'border-red-600 dark:border-red-500',
|
||||
warning: 'border-yellow-600 dark:border-yellow-500',
|
||||
info: "border-blue-600 border-blue-600 dark:border-pavitra-blue",
|
||||
success: 'border-gray-600 dark:border-gray-700',
|
||||
danger: 'border-gray-600 dark:border-gray-500',
|
||||
warning: 'border-gray-600 dark:border-gray-500',
|
||||
info: "border-gray-600 border-gray-600 dark:border-gray-700",
|
||||
},
|
||||
text: {
|
||||
contrast: 'dark:text-slate-100',
|
||||
success: 'text-emerald-600 dark:text-pavitra-blue',
|
||||
danger: 'text-red-600 dark:text-red-500',
|
||||
warning: 'text-yellow-600 dark:text-yellow-500',
|
||||
info: 'text-blue-600 dark:text-pavitra-blue',
|
||||
success: 'text-gray-600 dark:text-gray-400',
|
||||
danger: 'text-gray-600 dark:text-gray-400',
|
||||
warning: 'text-gray-600 dark:text-gray-400',
|
||||
info: 'text-gray-600 dark:text-gray-400',
|
||||
},
|
||||
outlineHover: {
|
||||
contrast:
|
||||
'hover:bg-gray-800 hover:text-gray-100 hover:dark:bg-slate-100 hover:dark:text-black',
|
||||
success: 'hover:bg-emerald-600 hover:text-white hover:text-white hover:dark:text-white hover:dark:border-pavitra-blue',
|
||||
success: 'hover:bg-gray-600 hover:text-white hover:text-white hover:dark:text-white hover:dark:border-gray-700',
|
||||
danger:
|
||||
'hover:bg-red-600 hover:text-white hover:text-white hover:dark:text-white hover:dark:border-red-600',
|
||||
'hover:bg-gray-600 hover:text-white hover:text-white hover:dark:text-white hover:dark:border-gray-600',
|
||||
warning:
|
||||
'hover:bg-yellow-600 hover:text-white hover:text-white hover:dark:text-white hover:dark:border-yellow-600',
|
||||
info: "hover:bg-blue-600 hover:bg-blue-600 hover:text-white hover:dark:text-white hover:dark:border-pavitra-blue",
|
||||
'hover:bg-gray-600 hover:text-white hover:text-white hover:dark:text-white hover:dark:border-gray-600',
|
||||
info: "hover:bg-gray-600 hover:bg-gray-600 hover:text-white hover:dark:text-white hover:dark:border-gray-700",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@ -8,5 +8,5 @@ type Props = {
|
||||
}
|
||||
|
||||
export default function CardBoxComponentBody({ noPadding = false, className, children, id }: Props) {
|
||||
return <div id={id} className={`flex-1 ${noPadding ? '' : 'p-6'} ${className}`}>{children}</div>
|
||||
return <div id={id} className={`flex-1 ${noPadding ? '' : 'p-2'} ${className}`}>{children}</div>
|
||||
}
|
||||
|
||||
@ -27,14 +27,14 @@ const FormField = ({ icons = [], ...props }: Props) => {
|
||||
|
||||
switch (childrenCount) {
|
||||
case 2:
|
||||
elementWrapperClass = 'grid grid-cols-1 gap-3 md:grid-cols-2'
|
||||
elementWrapperClass = 'grid grid-cols-1 gap-1 md:grid-cols-2'
|
||||
break
|
||||
case 3:
|
||||
elementWrapperClass = 'grid grid-cols-1 gap-3 md:grid-cols-3'
|
||||
elementWrapperClass = 'grid grid-cols-1 gap-1 md:grid-cols-3'
|
||||
}
|
||||
|
||||
const controlClassName = [
|
||||
`px-3 py-2 max-w-full border-gray-300 dark:border-dark-700 ${corners} w-full dark:placeholder-gray-400`,
|
||||
`px-2 py-1 max-w-full border-gray-300 dark:border-dark-700 ${corners} w-full dark:placeholder-gray-400`,
|
||||
`${focusRing}`,
|
||||
props.hasTextareaHeight ? 'h-24' : 'h-12',
|
||||
props.isBorderless ? 'border-0' : 'border',
|
||||
@ -44,11 +44,11 @@ const FormField = ({ icons = [], ...props }: Props) => {
|
||||
].join(' ');
|
||||
|
||||
return (
|
||||
<div className="mb-6 last:mb-0">
|
||||
<div className="mb-2 last:mb-0">
|
||||
{props.label && (
|
||||
<label
|
||||
htmlFor={props.labelFor}
|
||||
className={`block font-bold mb-2 ${props.labelFor ? 'cursor-pointer' : ''}`}
|
||||
className={`block font-bold mb-1 ${props.labelFor ? 'cursor-pointer' : ''}`}
|
||||
>
|
||||
{props.label}
|
||||
</label>
|
||||
|
||||
93
frontend/src/components/InfoCard.module.css
Normal file
93
frontend/src/components/InfoCard.module.css
Normal file
@ -0,0 +1,93 @@
|
||||
|
||||
.info-card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
border-radius: 1rem;
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.info-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.info-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -150%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
rgba(255, 255, 255, 0.4),
|
||||
transparent
|
||||
);
|
||||
transition: left 0.8s ease;
|
||||
}
|
||||
|
||||
.info-card:hover::before {
|
||||
left: 150%;
|
||||
}
|
||||
|
||||
.info-card-content {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.info-card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.info-card-title {
|
||||
font-size: 1.125rem;
|
||||
line-height: 1.75rem;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.dark .info-card-title {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.info-card-wrench {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.info-card-value {
|
||||
font-size: 1.875rem;
|
||||
line-height: 2.25rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.info-card-icon {
|
||||
z-index: 1;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.dark .info-card {
|
||||
background: #2d3748;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.dark .info-card:hover {
|
||||
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.dark .info-card::before {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
rgba(255, 255, 255, 0.1),
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
41
frontend/src/components/InfoCard.tsx
Normal file
41
frontend/src/components/InfoCard.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
|
||||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import BaseIcon from './BaseIcon';
|
||||
import { useAppSelector } from '../stores/hooks';
|
||||
import styles from './InfoCard.module.css';
|
||||
|
||||
type InfoCardProps = {
|
||||
href: string;
|
||||
title: string;
|
||||
value: string | number;
|
||||
icon: string;
|
||||
WrenchIcon?: any;
|
||||
};
|
||||
|
||||
const InfoCard = ({ href, title, value, icon, WrenchIcon }: InfoCardProps) => {
|
||||
const iconsColor = useAppSelector((state) => state.style.iconsColor);
|
||||
|
||||
return (
|
||||
<Link href={href}>
|
||||
<div className={styles['info-card']}>
|
||||
<div className={styles['info-card-content']}>
|
||||
<div className={styles['info-card-header']}>
|
||||
<div className={styles['info-card-title']}>{title}</div>
|
||||
{WrenchIcon && (
|
||||
<div className={styles['info-card-wrench']}>
|
||||
<WrenchIcon />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles['info-card-value']}>{value}</div>
|
||||
</div>
|
||||
<div className={styles['info-card-icon']}>
|
||||
<BaseIcon className={iconsColor} w="w-16" h="h-16" size={48} path={icon} />
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export default InfoCard;
|
||||
@ -6,5 +6,5 @@ type Props = {
|
||||
}
|
||||
|
||||
export default function SectionMain({ children }: Props) {
|
||||
return <section className={`p-6 ${containerMaxW}`}>{children}</section>
|
||||
return <section className={`p-2 ${containerMaxW}`}>{children}</section>
|
||||
}
|
||||
|
||||
@ -17,7 +17,7 @@ const SectionTitle = ({ custom = false, first = false, last = false, children }:
|
||||
}
|
||||
|
||||
return (
|
||||
<section className={`py-24 px-6 lg:px-0 lg:max-w-2xl lg:mx-auto text-center ${classAddon}`}>
|
||||
<section className={`py-8 px-2 lg:px-0 lg:max-w-2xl lg:mx-auto text-center ${classAddon}`}>
|
||||
{custom && children}
|
||||
{!custom && <h1 className="text-2xl text-gray-500 dark:text-slate-400">{children}</h1>}
|
||||
</section>
|
||||
|
||||
79
frontend/src/components/TableSampleTasks.tsx
Normal file
79
frontend/src/components/TableSampleTasks.tsx
Normal file
@ -0,0 +1,79 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useAppDispatch, useAppSelector } from '../stores/hooks'
|
||||
import { fetch } from '../stores/tasks/tasksSlice'
|
||||
import { Task } from '../interfaces'
|
||||
import BaseButton from './BaseButton'
|
||||
import BaseButtons from './BaseButtons'
|
||||
import CardBox from './CardBox'
|
||||
|
||||
const TableSampleTasks = () => {
|
||||
const dispatch = useAppDispatch()
|
||||
const { currentUser } = useAppSelector((state) => state.auth)
|
||||
const { tasks } = useAppSelector((state) => state.tasks)
|
||||
|
||||
useEffect(() => {
|
||||
if (currentUser) {
|
||||
dispatch(fetch({ query: `?assignee=${currentUser.id}&sort=due_date&order=asc` }))
|
||||
}
|
||||
}, [dispatch, currentUser])
|
||||
|
||||
const perPage = 5
|
||||
const [currentPage, setCurrentPage] = useState(0)
|
||||
|
||||
const tasksPaginated = tasks.slice(perPage * currentPage, perPage * (currentPage + 1))
|
||||
const numPages = Math.ceil(tasks.length / perPage)
|
||||
const pagesList = []
|
||||
for (let i = 0; i < numPages; i++) {
|
||||
pagesList.push(i)
|
||||
}
|
||||
|
||||
return (
|
||||
<CardBox className="mb-6">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>Project</th>
|
||||
<th>Status</th>
|
||||
<th>Due Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{tasksPaginated.map((task: Task) => (
|
||||
<tr key={task.id}>
|
||||
<td data-label="Title">{task.title}</td>
|
||||
<td data-label="Project">{task.project?.name}</td>
|
||||
<td data-label="Status">{task.status}</td>
|
||||
<td data-label="Due Date" className="lg:w-1 whitespace-nowrap">
|
||||
<small className="text-gray-500 dark:text-slate-400">
|
||||
{new Date(task.due_date).toLocaleDateString()}
|
||||
</small>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="p-3 lg:px-6 border-t border-gray-100 dark:border-slate-800">
|
||||
<div className="flex flex-col md:flex-row items-center justify-between py-3 md:py-0">
|
||||
<BaseButtons>
|
||||
{pagesList.map((page) => (
|
||||
<BaseButton
|
||||
key={page}
|
||||
active={page === currentPage}
|
||||
label={page + 1}
|
||||
color={page === currentPage ? 'lightDark' : 'whiteDark'}
|
||||
small
|
||||
onClick={() => setCurrentPage(page)}
|
||||
/>
|
||||
))}
|
||||
</BaseButtons>
|
||||
<small className="mt-6 md:mt-0">
|
||||
Page {currentPage + 1} of {numPages}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</CardBox>
|
||||
)
|
||||
}
|
||||
|
||||
export default TableSampleTasks
|
||||
@ -107,3 +107,24 @@ export type UserForm = {
|
||||
name: string
|
||||
email: string
|
||||
}
|
||||
|
||||
export interface Project {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface Task {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
status: 'todo' | 'in_progress' | 'done';
|
||||
deadline: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
assignee?: User;
|
||||
project?: Project;
|
||||
due_date: string;
|
||||
}
|
||||
|
||||
@ -7,6 +7,16 @@ const menuAside: MenuAsideItem[] = [
|
||||
icon: icon.mdiViewDashboardOutline,
|
||||
label: 'Dashboard',
|
||||
},
|
||||
{
|
||||
href: '/test',
|
||||
label: 'Test Page',
|
||||
icon: icon.mdiTestTube,
|
||||
},
|
||||
{
|
||||
href: '/project-statistics',
|
||||
label: 'Project Statistics',
|
||||
icon: icon.mdiChartBar,
|
||||
},
|
||||
|
||||
{
|
||||
href: '/users/users-list',
|
||||
|
||||
@ -9,11 +9,13 @@ import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton
|
||||
import BaseIcon from "../components/BaseIcon";
|
||||
import { getPageTitle } from '../config'
|
||||
import Link from "next/link";
|
||||
import InfoCard from '../components/InfoCard';
|
||||
|
||||
import { hasPermission } from "../helpers/userPermissions";
|
||||
import { fetchWidgets } from '../stores/roles/rolesSlice';
|
||||
import { WidgetCreator } from '../components/WidgetCreator/WidgetCreator';
|
||||
import { SmartWidget } from '../components/SmartWidget/SmartWidget';
|
||||
import TableSampleTasks from '../components/TableSampleTasks';
|
||||
|
||||
import { useAppDispatch, useAppSelector } from '../stores/hooks';
|
||||
const Dashboard = () => {
|
||||
@ -140,235 +142,76 @@ const Dashboard = () => {
|
||||
|
||||
{!!rolesWidgets.length && <hr className='my-6 ' />}
|
||||
|
||||
<div id="dashboard" className='grid grid-cols-1 gap-6 lg:grid-cols-3 mb-6'>
|
||||
|
||||
|
||||
{hasPermission(currentUser, 'READ_USERS') && <Link href={'/users/users-list'}>
|
||||
<div
|
||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Users
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{users}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w="w-16"
|
||||
h="h-16"
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={icon.mdiAccountGroup || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
{hasPermission(currentUser, 'READ_ROLES') && <Link href={'/roles/roles-list'}>
|
||||
<div
|
||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Roles
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{roles}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w="w-16"
|
||||
h="h-16"
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={icon.mdiShieldAccountVariantOutline || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
{hasPermission(currentUser, 'READ_PERMISSIONS') && <Link href={'/permissions/permissions-list'}>
|
||||
<div
|
||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Permissions
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{permissions}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w="w-16"
|
||||
h="h-16"
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={icon.mdiShieldAccountOutline || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
{hasPermission(currentUser, 'READ_TEAMS') && <Link href={'/teams/teams-list'}>
|
||||
<div
|
||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Teams
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{teams}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w="w-16"
|
||||
h="h-16"
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={'mdiAccountGroup' in icon ? icon['mdiAccountGroup' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
{hasPermission(currentUser, 'READ_PROJECTS') && <Link href={'/projects/projects-list'}>
|
||||
<div
|
||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Projects
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{projects}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w="w-16"
|
||||
h="h-16"
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={'mdiFolderMultiple' in icon ? icon['mdiFolderMultiple' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
{hasPermission(currentUser, 'READ_TASKS') && <Link href={'/tasks/tasks-list'}>
|
||||
<div
|
||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Tasks
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{tasks}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w="w-16"
|
||||
h="h-16"
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={'mdiClipboardText' in icon ? icon['mdiClipboardText' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
{hasPermission(currentUser, 'READ_COMMENTS') && <Link href={'/comments/comments-list'}>
|
||||
<div
|
||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Comments
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{comments}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w="w-16"
|
||||
h="h-16"
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={'mdiCommentText' in icon ? icon['mdiCommentText' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
{hasPermission(currentUser, 'READ_REPORTS') && <Link href={'/reports/reports-list'}>
|
||||
<div
|
||||
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className="flex justify-between align-center">
|
||||
<div>
|
||||
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
|
||||
Reports
|
||||
</div>
|
||||
<div className="text-3xl leading-tight font-semibold">
|
||||
{reports}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w="w-16"
|
||||
h="h-16"
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={'mdiFileDocument' in icon ? icon['mdiFileDocument' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>}
|
||||
|
||||
|
||||
</div>
|
||||
<div id="dashboard" className="grid grid-cols-1 gap-6 lg:grid-cols-3 mb-6">
|
||||
{hasPermission(currentUser, 'READ_USERS') && (
|
||||
<InfoCard
|
||||
href="/users/users-list"
|
||||
title="Users"
|
||||
value={users}
|
||||
icon={icon.mdiAccountGroup}
|
||||
/>
|
||||
)}
|
||||
{hasPermission(currentUser, 'READ_ROLES') && (
|
||||
<InfoCard
|
||||
href="/roles/roles-list"
|
||||
title="Roles"
|
||||
value={roles}
|
||||
icon={icon.mdiShieldAccountVariantOutline}
|
||||
/>
|
||||
)}
|
||||
{hasPermission(currentUser, 'READ_PERMISSIONS') && (
|
||||
<InfoCard
|
||||
href="/permissions/permissions-list"
|
||||
title="Permissions"
|
||||
value={permissions}
|
||||
icon={icon.mdiShieldAccountOutline}
|
||||
/>
|
||||
)}
|
||||
{hasPermission(currentUser, 'READ_TEAMS') && (
|
||||
<InfoCard
|
||||
href="/teams/teams-list"
|
||||
title="Teams"
|
||||
value={teams}
|
||||
icon={icon.mdiAccountGroup}
|
||||
/>
|
||||
)}
|
||||
{hasPermission(currentUser, 'READ_PROJECTS') && (
|
||||
<InfoCard
|
||||
href="/projects/projects-list"
|
||||
title="Projects"
|
||||
value={projects}
|
||||
icon={icon.mdiFolderMultiple}
|
||||
/>
|
||||
)}
|
||||
{hasPermission(currentUser, 'READ_TASKS') && (
|
||||
<InfoCard
|
||||
href="/tasks/tasks-list"
|
||||
title="Tasks"
|
||||
value={tasks}
|
||||
icon={icon.mdiClipboardText}
|
||||
/>
|
||||
)}
|
||||
{hasPermission(currentUser, 'READ_COMMENTS') && (
|
||||
<InfoCard
|
||||
href="/comments/comments-list"
|
||||
title="Comments"
|
||||
value={comments}
|
||||
icon={icon.mdiCommentText}
|
||||
/>
|
||||
)}
|
||||
{hasPermission(currentUser, 'READ_REPORTS') && (
|
||||
<InfoCard
|
||||
href="/reports/reports-list"
|
||||
title="Reports"
|
||||
value={reports}
|
||||
icon={icon.mdiFileDocument}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<SectionTitleLineWithButton icon={icon.mdiClipboardText} title="My Tasks" />
|
||||
<TableSampleTasks />
|
||||
|
||||
</SectionMain>
|
||||
</>
|
||||
)
|
||||
|
||||
175
frontend/src/pages/project-statistics.tsx
Normal file
175
frontend/src/pages/project-statistics.tsx
Normal file
@ -0,0 +1,175 @@
|
||||
import React, { ReactElement, useEffect, useState, useRef } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import LayoutAuthenticated from '../layouts/Authenticated';
|
||||
import CardBox from '../components/CardBox';
|
||||
import { fetch } from '../stores/projects/projectsSlice';
|
||||
import { RootState } from '../stores/store';
|
||||
import SectionMain from '../components/SectionMain';
|
||||
import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton';
|
||||
import FormField from '../components/FormField';
|
||||
import BaseButton from '../components/BaseButton';
|
||||
import { mdiChartTimelineVariant } from '@mdi/js';
|
||||
import Head from 'next/head';
|
||||
import { getPageTitle } from '../config';
|
||||
import { aiResponse, resetAiResponse } from '../stores/openAiSlice';
|
||||
|
||||
const ProjectStatisticsPage = () => {
|
||||
const dispatch = useDispatch();
|
||||
const { projects, loading } = useSelector((state: RootState) => state.projects);
|
||||
const { aiResponse: aiResult, isAskingResponse, errorMessage } = useSelector((state: RootState) => state.openAi);
|
||||
|
||||
const [messages, setMessages] = useState([
|
||||
{ text: 'Hello! How can I help you today?', sender: 'ai' },
|
||||
]);
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const [isTyping, setIsTyping] = useState(false);
|
||||
const chatMessagesContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetch({}));
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (chatMessagesContainerRef.current) {
|
||||
chatMessagesContainerRef.current.scrollTop = chatMessagesContainerRef.current.scrollHeight;
|
||||
}
|
||||
}, [messages, isTyping]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isAskingResponse) {
|
||||
setIsTyping(true);
|
||||
} else {
|
||||
setIsTyping(false);
|
||||
}
|
||||
|
||||
if (aiResult) {
|
||||
const text = aiResult.text;
|
||||
if (text) {
|
||||
setMessages((prevMessages) => [...prevMessages, { text, sender: 'ai' }]);
|
||||
} else {
|
||||
const errorMessageText = aiResult.message || 'An error occurred processing the AI response.';
|
||||
setMessages((prevMessages) => [...prevMessages, { text: errorMessageText, sender: 'ai' }]);
|
||||
}
|
||||
dispatch(resetAiResponse());
|
||||
}
|
||||
|
||||
if (errorMessage) {
|
||||
setMessages((prevMessages) => [...prevMessages, { text: errorMessage, sender: 'ai' }]);
|
||||
dispatch(resetAiResponse());
|
||||
}
|
||||
}, [aiResult, isAskingResponse, errorMessage, dispatch]);
|
||||
|
||||
const handleSendMessage = () => {
|
||||
if (inputValue.trim()) {
|
||||
const userMessage = { text: inputValue, sender: 'user' as const };
|
||||
setMessages([...messages, userMessage]);
|
||||
|
||||
const payload = {
|
||||
input: [...messages.map(m => ({ role: m.sender === 'user' ? 'user' : 'assistant', content: m.text })), { role: 'user', content: inputValue }],
|
||||
};
|
||||
|
||||
setInputValue('');
|
||||
dispatch(resetAiResponse());
|
||||
dispatch(aiResponse(payload));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Project Statistics')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton icon={mdiChartTimelineVariant} title="Project Statistics" main>
|
||||
</SectionTitleLineWithButton>
|
||||
|
||||
<CardBox className="mb-6">
|
||||
{loading ? (
|
||||
<p>Loading...</p>
|
||||
) : (
|
||||
<p>Total projects: {projects.length}</p>
|
||||
)}
|
||||
</CardBox>
|
||||
|
||||
<CardBox>
|
||||
<div className="flex flex-col h-[500px]">
|
||||
{/* Chat messages area */}
|
||||
<div ref={chatMessagesContainerRef} className="flex-grow p-4 overflow-y-auto">
|
||||
{messages.map((message, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`flex items-end mb-4 ${
|
||||
message.sender === 'user' ? 'justify-end' : ''
|
||||
}`}
|
||||
>
|
||||
{message.sender === 'ai' && (
|
||||
<div className="flex-shrink-0">
|
||||
{/* Placeholder for AI avatar */}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={`p-3 rounded-lg ${
|
||||
message.sender === 'user'
|
||||
? 'bg-blue-500 text-white mr-3'
|
||||
: 'bg-gray-200 dark:bg-slate-700 ml-3'
|
||||
}`}
|
||||
>
|
||||
<p className="text-sm">{message.text}</p>
|
||||
</div>
|
||||
{message.sender === 'user' && (
|
||||
<div className="flex-shrink-0">
|
||||
{/* Placeholder for user avatar */}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{isTyping && (
|
||||
<div className="flex items-end mb-4">
|
||||
<div className="flex-shrink-0"></div>
|
||||
<div className="ml-3 p-3 rounded-lg bg-gray-200 dark:bg-slate-700">
|
||||
<p className="text-sm">AI is typing...</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Chat input */}
|
||||
<div className="p-4 bg-gray-100 dark:bg-slate-800">
|
||||
<div className="flex items-center">
|
||||
<FormField>
|
||||
<input
|
||||
className="w-full bg-transparent p-2 border border-gray-300 dark:border-slate-600 rounded-lg"
|
||||
placeholder="Type your message..."
|
||||
value={inputValue}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
onKeyPress={(e) => e.key === 'Enter' && handleSendMessage()}
|
||||
disabled={isAskingResponse}
|
||||
/>
|
||||
</FormField>
|
||||
<BaseButton
|
||||
label="Send"
|
||||
color="info"
|
||||
className="ml-2"
|
||||
onClick={handleSendMessage}
|
||||
disabled={isAskingResponse}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
ProjectStatisticsPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated
|
||||
permission={'READ_PROJECTS'}
|
||||
>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProjectStatisticsPage;
|
||||
60
frontend/src/pages/test.tsx
Normal file
60
frontend/src/pages/test.tsx
Normal file
@ -0,0 +1,60 @@
|
||||
|
||||
import { NextPage } from 'next'
|
||||
import Head from 'next/head'
|
||||
import SectionTitle from '../components/SectionTitle'
|
||||
import LayoutAuthenticated from '../layouts/Authenticated'
|
||||
import CardBox from '../components/CardBox'
|
||||
|
||||
const TestPage: NextPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Test Page - Flatlogic</title>
|
||||
</Head>
|
||||
|
||||
<SectionTitle>Test Page</SectionTitle>
|
||||
|
||||
<CardBox className="ml-6">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex-grow p-4 overflow-y-auto">
|
||||
{/* Message list will go here */}
|
||||
<div className="flex items-end mb-4">
|
||||
<div className="flex-shrink-0 w-8 h-8 bg-gray-300 rounded-full"></div>
|
||||
<div className="ml-3 p-3 bg-gray-200 rounded-lg">
|
||||
<p className="text-sm">Hello!</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-end justify-end mb-4">
|
||||
<div className="mr-3 p-3 bg-blue-500 text-white rounded-lg">
|
||||
<p className="text-sm">Hi there!</p>
|
||||
</div>
|
||||
<div className="flex-shrink-0 w-8 h-8 bg-gray-300 rounded-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-4 bg-gray-100">
|
||||
<div className="flex items-center">
|
||||
<div className="flex-grow">
|
||||
<input
|
||||
type="text"
|
||||
className="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring focus:border-blue-300"
|
||||
placeholder="Type your message..."
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<button
|
||||
className="px-4 py-2 text-white bg-blue-500 rounded-lg hover:bg-blue-600 focus:outline-none focus:ring focus:bg-blue-600"
|
||||
>
|
||||
Send
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardBox>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
TestPage.getLayout = (page) => <LayoutAuthenticated>{page}</LayoutAuthenticated>
|
||||
|
||||
export default TestPage
|
||||
@ -92,6 +92,10 @@ export const openAiSlice = createSlice({
|
||||
setErrorNotification: (state, action) => {
|
||||
fulfilledNotify(state, action.payload, 'error');
|
||||
},
|
||||
resetAiResponse: (state) => {
|
||||
state.aiResponse = null;
|
||||
state.errorMessage = '';
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(aiPrompt.pending, (state) => {
|
||||
@ -146,6 +150,6 @@ export const openAiSlice = createSlice({
|
||||
});
|
||||
|
||||
// Action creators are generated for each case reducer function
|
||||
export const { resetNotify, setErrorNotification } = openAiSlice.actions;
|
||||
export const { resetNotify, setErrorNotification, resetAiResponse } = openAiSlice.actions;
|
||||
|
||||
export default openAiSlice.reducer;
|
||||
|
||||
@ -39,8 +39,7 @@ export const white: StyleObject = {
|
||||
bgLayoutColor: 'bg-gray-50',
|
||||
iconsColor: 'text-blue-500',
|
||||
cardsColor: 'bg-white',
|
||||
focusRingColor: 'focus:ring focus:ring-blue-600 focus:border-blue-600 focus:outline-none border-gray-300 dark:focus:ring-blue-600 dark:focus:border-blue-600',
|
||||
corners: 'rounded',
|
||||
corners: '',
|
||||
cardsStyle: 'bg-white border border-pavitra-400',
|
||||
linkColor: 'text-blue-600',
|
||||
websiteHeder: 'border-b border-gray-200',
|
||||
@ -67,13 +66,13 @@ export const dataGridStyles = {
|
||||
},
|
||||
'& .MuiDataGrid-columnHeaders': {
|
||||
paddingY: 4,
|
||||
borderStartStartRadius: 7,
|
||||
borderStartEndRadius: 7,
|
||||
borderStartStartRadius: 0,
|
||||
borderStartEndRadius: 0,
|
||||
},
|
||||
'& .MuiDataGrid-footerContainer': {
|
||||
paddingY: 0.5,
|
||||
borderEndStartRadius: 7,
|
||||
borderEndEndRadius: 7,
|
||||
borderEndStartRadius: 0,
|
||||
borderEndEndRadius: 0,
|
||||
},
|
||||
'& .MuiDataGrid-root': {
|
||||
border: 'none',
|
||||
@ -96,7 +95,7 @@ export const basic: StyleObject = {
|
||||
iconsColor: 'text-blue-500',
|
||||
cardsColor: 'bg-white',
|
||||
focusRingColor: 'focus:ring focus:ring-blue-600 focus:border-blue-600 focus:outline-none dark:focus:ring-blue-600 border-gray-300 dark:focus:border-blue-600',
|
||||
corners: 'rounded',
|
||||
corners: '',
|
||||
cardsStyle: 'bg-white border border-pavitra-400',
|
||||
linkColor: 'text-black',
|
||||
websiteHeder: '',
|
||||
|
||||
@ -68,8 +68,15 @@ module.exports = {
|
||||
|
||||
|
||||
},
|
||||
borderRadius: {
|
||||
'3xl': '2rem',
|
||||
borderRadius: {
|
||||
'none': '0',
|
||||
'sm': '0',
|
||||
'md': '0',
|
||||
'lg': '0',
|
||||
'xl': '0',
|
||||
'2xl': '0',
|
||||
'3xl': '0',
|
||||
DEFAULT: '0'
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user