Introduce prettier

This commit is contained in:
Takeshi Kimata
2024-05-19 21:06:49 +09:00
parent 75eeebefda
commit c5ba3b9a00
52 changed files with 884 additions and 1509 deletions

View File

@@ -1,17 +1,8 @@
"use client";
import React from "react";
import { useState, useEffect } from "react";
import {
Button,
Input,
Textarea,
Modal,
ModalContent,
ModalHeader,
ModalBody,
ModalFooter,
} from "@nextui-org/react";
import { ProjectType, ProjectsMessages } from "@/types/project";
'use client';
import React from 'react';
import { useState, useEffect } from 'react';
import { Button, Input, Textarea, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter } from '@nextui-org/react';
import { ProjectType, ProjectsMessages } from '@/types/project';
type Props = {
isOpen: boolean;
@@ -21,23 +12,17 @@ type Props = {
messages: ProjectsMessages;
};
export default function ProjectDialog({
isOpen,
editingProject,
onCancel,
onSubmit,
messages,
}: Props) {
export default function ProjectDialog({ isOpen, editingProject, onCancel, onSubmit, messages }: Props) {
const [projectName, setProjectName] = useState({
text: editingProject ? editingProject.name : "",
text: editingProject ? editingProject.name : '',
isValid: false,
errorMessage: "",
errorMessage: '',
});
const [projectDetail, setProjectDetail] = useState({
text: editingProject ? editingProject.detail : "",
text: editingProject ? editingProject.detail : '',
isValid: false,
errorMessage: "",
errorMessage: '',
});
useEffect(() => {
@@ -49,17 +34,17 @@ export default function ProjectDialog({
setProjectDetail({
...projectDetail,
text: editingProject.detail ? editingProject.detail : "",
text: editingProject.detail ? editingProject.detail : '',
});
} else {
setProjectName({
...projectName,
text: "",
text: '',
});
setProjectDetail({
...projectDetail,
text: "",
text: '',
});
}
}, [editingProject]);
@@ -67,20 +52,20 @@ export default function ProjectDialog({
const clear = () => {
setProjectName({
isValid: false,
text: "",
errorMessage: "",
text: '',
errorMessage: '',
});
setProjectDetail({
isValid: false,
text: "",
errorMessage: "",
text: '',
errorMessage: '',
});
};
const validate = () => {
if (!projectName.text) {
setProjectName({
text: "",
text: '',
isValid: false,
errorMessage: messages.pleaseEnter,
});
@@ -100,9 +85,7 @@ export default function ProjectDialog({
}}
>
<ModalContent>
<ModalHeader className="flex flex-col gap-1">
{messages.project}
</ModalHeader>
<ModalHeader className="flex flex-col gap-1">{messages.project}</ModalHeader>
<ModalBody>
<Input
type="text"

View File

@@ -1,16 +1,11 @@
"use client";
import { useEffect, useState } from "react";
import { Button } from "@nextui-org/react";
import { Plus } from "lucide-react";
import { ProjectType, ProjectsMessages } from "@/types/project";
import ProjectsTable from "./ProjectsTable";
import ProjectDialog from "./ProjectDialog";
import {
fetchProjects,
createProject,
updateProject,
deleteProject,
} from "./projectsControl";
'use client';
import { useEffect, useState } from 'react';
import { Button } from '@nextui-org/react';
import { Plus } from 'lucide-react';
import { ProjectType, ProjectsMessages } from '@/types/project';
import ProjectsTable from './ProjectsTable';
import ProjectDialog from './ProjectDialog';
import { fetchProjects, createProject, updateProject, deleteProject } from './projectsControl';
export type Props = {
messages: ProjectsMessages;
@@ -26,7 +21,7 @@ export default function ProjectsPage({ messages, locale }: Props) {
const data = await fetchProjects();
setProjects(data);
} catch (error: any) {
console.error("Error in effect:", error.message);
console.error('Error in effect:', error.message);
}
}
@@ -35,9 +30,7 @@ export default function ProjectsPage({ messages, locale }: Props) {
// dialog
const [isProjectDialogOpen, setIsProjectDialogOpen] = useState(false);
const [editingProject, setEditingProject] = useState<ProjectType | null>(
null
);
const [editingProject, setEditingProject] = useState<ProjectType | null>(null);
const openDialogForCreate = () => {
setIsProjectDialogOpen(true);
setEditingProject(null);
@@ -50,14 +43,8 @@ export default function ProjectsPage({ messages, locale }: Props) {
const onSubmit = async (name: string, detail: string) => {
if (editingProject) {
const updatedProject = await updateProject(
editingProject.id,
name,
detail
);
const updatedProjects = projects.map((project) =>
project.id === updatedProject.id ? updatedProject : project
);
const updatedProject = await updateProject(editingProject.id, name, detail);
const updatedProjects = projects.map((project) => (project.id === updatedProject.id ? updatedProject : project));
setProjects(updatedProjects);
} else {
const newProject = await createProject(name, detail);
@@ -76,7 +63,7 @@ export default function ProjectsPage({ messages, locale }: Props) {
await deleteProject(projectId);
setProjects(projects.filter((project) => project.id !== projectId));
} catch (error: any) {
console.error("Error deleting project:", error);
console.error('Error deleting project:', error);
}
};
@@ -85,12 +72,7 @@ export default function ProjectsPage({ messages, locale }: Props) {
<div className="w-full p-3 flex items-center justify-between">
<h3 className="font-bold">{messages.projectList}</h3>
<div>
<Button
startContent={<Plus size={16} />}
size="sm"
color="primary"
onClick={openDialogForCreate}
>
<Button startContent={<Plus size={16} />} size="sm" color="primary" onClick={openDialogForCreate}>
{messages.newProject}
</Button>
</div>

View File

@@ -1,4 +1,4 @@
import { useState, useMemo, useCallback } from "react";
import { useState, useMemo, useCallback } from 'react';
import {
Table,
TableHeader,
@@ -12,11 +12,11 @@ import {
DropdownMenu,
DropdownItem,
SortDescriptor,
} from "@nextui-org/react";
import { Link, NextUiLinkClasses } from "@/src/navigation";
import { MoreVertical } from "lucide-react";
import { ProjectType, ProjectsMessages } from "@/types/project";
import dayjs from "dayjs";
} from '@nextui-org/react';
import { Link, NextUiLinkClasses } from '@/src/navigation';
import { MoreVertical } from 'lucide-react';
import { ProjectType, ProjectsMessages } from '@/types/project';
import dayjs from 'dayjs';
type Props = {
projects: ProjectType[];
@@ -26,24 +26,18 @@ type Props = {
locale: string;
};
export default function ProjectsTable({
projects,
onEditProject,
onDeleteProject,
messages,
locale,
}: Props) {
export default function ProjectsTable({ projects, onEditProject, onDeleteProject, messages, locale }: Props) {
const headerColumns = [
{ name: messages.id, uid: "id", sortable: true },
{ name: messages.name, uid: "name", sortable: true },
{ name: messages.detail, uid: "detail", sortable: true },
{ name: messages.lastUpdate, uid: "updatedAt", sortable: true },
{ name: messages.actions, uid: "actions" },
{ name: messages.id, uid: 'id', sortable: true },
{ name: messages.name, uid: 'name', sortable: true },
{ name: messages.detail, uid: 'detail', sortable: true },
{ name: messages.lastUpdate, uid: 'updatedAt', sortable: true },
{ name: messages.actions, uid: 'actions' },
];
const [sortDescriptor, setSortDescriptor] = useState<SortDescriptor>({
column: "id",
direction: "ascending",
column: 'id',
direction: 'ascending',
});
const sortedItems = useMemo(() => {
@@ -52,31 +46,27 @@ export default function ProjectsTable({
const second = b[sortDescriptor.column as keyof ProjectType] as number;
const cmp = first < second ? -1 : first > second ? 1 : 0;
return sortDescriptor.direction === "descending" ? -cmp : cmp;
return sortDescriptor.direction === 'descending' ? -cmp : cmp;
});
}, [sortDescriptor, projects]);
const truncateText = (text: string, maxLength: number) => {
return text.length > maxLength ? text.slice(0, maxLength) + "..." : text;
return text.length > maxLength ? text.slice(0, maxLength) + '...' : text;
};
const renderCell = useCallback((project: ProjectType, columnKey: Key) => {
const cellValue = project[columnKey as keyof ProjectType];
switch (columnKey) {
case "id":
case 'id':
return <span>{cellValue}</span>;
case "name":
case 'name':
return (
<Link
href={`/projects/${project.id}/home`}
locale={locale}
className={NextUiLinkClasses}
>
<Link href={`/projects/${project.id}/home`} locale={locale} className={NextUiLinkClasses}>
{cellValue}
</Link>
);
case "detail":
case 'detail':
const maxLength = 20;
const truncatedValue = truncateText(cellValue, maxLength);
return (
@@ -84,9 +74,9 @@ export default function ProjectsTable({
<div>{truncatedValue}</div>
</div>
);
case "updatedAt":
return <span>{dayjs(cellValue).format("YYYY/MM/DD HH:mm")}</span>;
case "actions":
case 'updatedAt':
return <span>{dayjs(cellValue).format('YYYY/MM/DD HH:mm')}</span>;
case 'actions':
return (
<Dropdown>
<DropdownTrigger>
@@ -95,13 +85,8 @@ export default function ProjectsTable({
</Button>
</DropdownTrigger>
<DropdownMenu aria-label="project actions">
<DropdownItem onClick={() => onEditProject(project)}>
{messages.editProject}
</DropdownItem>
<DropdownItem
className="text-danger"
onClick={() => onDeleteProject(project.id)}
>
<DropdownItem onClick={() => onEditProject(project)}>{messages.editProject}</DropdownItem>
<DropdownItem className="text-danger" onClick={() => onDeleteProject(project.id)}>
{messages.deleteProject}
</DropdownItem>
</DropdownMenu>
@@ -114,18 +99,18 @@ export default function ProjectsTable({
const classNames = useMemo(
() => ({
wrapper: ["max-w-3xl"],
th: ["bg-transparent", "text-default-500", "border-b", "border-divider"],
wrapper: ['max-w-3xl'],
th: ['bg-transparent', 'text-default-500', 'border-b', 'border-divider'],
td: [
// changing the rows border radius
// first
"group-data-[first=true]:first:before:rounded-none",
"group-data-[first=true]:last:before:rounded-none",
'group-data-[first=true]:first:before:rounded-none',
'group-data-[first=true]:last:before:rounded-none',
// middle
"group-data-[middle=true]:before:rounded-none",
'group-data-[middle=true]:before:rounded-none',
// last
"group-data-[last=true]:first:before:rounded-none",
"group-data-[last=true]:last:before:rounded-none",
'group-data-[last=true]:first:before:rounded-none',
'group-data-[last=true]:last:before:rounded-none',
],
}),
[]
@@ -144,7 +129,7 @@ export default function ProjectsTable({
{(column) => (
<TableColumn
key={column.uid}
align={column.uid === "actions" ? "center" : "start"}
align={column.uid === 'actions' ? 'center' : 'start'}
allowsSorting={column.sortable}
>
{column.name}
@@ -153,11 +138,7 @@ export default function ProjectsTable({
</TableHeader>
<TableBody emptyContent={messages.noProjectsFound} items={sortedItems}>
{(item) => (
<TableRow key={item.id}>
{(columnKey) => (
<TableCell>{renderCell(item, columnKey)}</TableCell>
)}
</TableRow>
<TableRow key={item.id}>{(columnKey) => <TableCell>{renderCell(item, columnKey)}</TableCell>}</TableRow>
)}
</TableBody>
</Table>

View File

@@ -1,10 +1,10 @@
"use client";
import { useState, useEffect } from "react";
import { Listbox, ListboxItem } from "@nextui-org/react";
import { Home, Files, FlaskConical } from "lucide-react";
import { usePathname, useRouter } from "@/src/navigation";
import useGetCurrentIds from "@/utils/useGetCurrentIds";
import { ProjectMessages } from "@/types/project";
'use client';
import { useState, useEffect } from 'react';
import { Listbox, ListboxItem } from '@nextui-org/react';
import { Home, Files, FlaskConical } from 'lucide-react';
import { usePathname, useRouter } from '@/src/navigation';
import useGetCurrentIds from '@/utils/useGetCurrentIds';
import { ProjectMessages } from '@/types/project';
export type Props = {
messages: ProjectMessages;
@@ -16,28 +16,28 @@ export default function Sidebar({ messages, locale }: Props) {
const router = useRouter();
const pathname = usePathname();
const [currentKey, setCurrentTab] = useState("home");
const baseClass = "p-3";
const [currentKey, setCurrentTab] = useState('home');
const baseClass = 'p-3';
const selectedClass = `${baseClass} bg-neutral-200 dark:bg-neutral-700`;
const handleTabClick = (key: string) => {
if (key === "home") {
if (key === 'home') {
router.push(`/projects/${projectId}/home`, { locale: locale });
} else if (key === "cases") {
} else if (key === 'cases') {
router.push(`/projects/${projectId}/folders`, { locale: locale });
} else if (key === "runs") {
} else if (key === 'runs') {
router.push(`/projects/${projectId}/runs`, { locale: locale });
}
};
useEffect(() => {
const handleRouteChange = (currentPath: string) => {
if (currentPath.includes("home")) {
setCurrentTab("home");
} else if (currentPath.includes("folders")) {
setCurrentTab("cases");
} else if (currentPath.includes("runs")) {
setCurrentTab("runs");
if (currentPath.includes('home')) {
setCurrentTab('home');
} else if (currentPath.includes('folders')) {
setCurrentTab('cases');
} else if (currentPath.includes('runs')) {
setCurrentTab('runs');
}
};
@@ -46,17 +46,17 @@ export default function Sidebar({ messages, locale }: Props) {
const tabItems = [
{
key: "home",
key: 'home',
text: messages.home,
startContent: <Home strokeWidth={1} size={28} />,
},
{
key: "cases",
key: 'cases',
text: messages.testCases,
startContent: <Files strokeWidth={1} size={28} />,
},
{
key: "runs",
key: 'runs',
text: messages.testRuns,
startContent: <FlaskConical strokeWidth={1} size={28} />,
},

View File

@@ -1,17 +1,8 @@
"use client";
import React from "react";
import { useState, useEffect } from "react";
import {
Button,
Input,
Textarea,
Modal,
ModalContent,
ModalHeader,
ModalBody,
ModalFooter,
} from "@nextui-org/react";
import { FolderType, FoldersMessages } from "@/types/folder";
'use client';
import React from 'react';
import { useState, useEffect } from 'react';
import { Button, Input, Textarea, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter } from '@nextui-org/react';
import { FolderType, FoldersMessages } from '@/types/folder';
type Props = {
isOpen: boolean;
@@ -21,23 +12,17 @@ type Props = {
messages: FoldersMessages;
};
export default function FolderDialog({
isOpen,
editingFolder,
onCancel,
onSubmit,
messages,
}: Props) {
export default function FolderDialog({ isOpen, editingFolder, onCancel, onSubmit, messages }: Props) {
const [folderName, setFolderName] = useState({
text: editingFolder ? editingFolder.name : "",
text: editingFolder ? editingFolder.name : '',
isValid: false,
errorMessage: "",
errorMessage: '',
});
const [folderDetail, setFolderDetail] = useState({
text: editingFolder ? editingFolder.detail : "",
text: editingFolder ? editingFolder.detail : '',
isValid: false,
errorMessage: "",
errorMessage: '',
});
useEffect(() => {
@@ -49,17 +34,17 @@ export default function FolderDialog({
setFolderDetail({
...folderDetail,
text: editingFolder.detail ? editingFolder.detail : "",
text: editingFolder.detail ? editingFolder.detail : '',
});
} else {
setFolderName({
...folderName,
text: "",
text: '',
});
setFolderDetail({
...folderDetail,
text: "",
text: '',
});
}
}, [editingFolder]);
@@ -67,20 +52,20 @@ export default function FolderDialog({
const clear = () => {
setFolderName({
isValid: false,
text: "",
errorMessage: "",
text: '',
errorMessage: '',
});
setFolderDetail({
isValid: false,
text: "",
errorMessage: "",
text: '',
errorMessage: '',
});
};
const validate = () => {
if (!folderName.text) {
setFolderName({
text: "",
text: '',
isValid: false,
errorMessage: messages.pleaseEnter,
});
@@ -100,9 +85,7 @@ export default function FolderDialog({
}}
>
<ModalContent>
<ModalHeader className="flex flex-col gap-1">
{messages.folder}
</ModalHeader>
<ModalHeader className="flex flex-col gap-1">{messages.folder}</ModalHeader>
<ModalBody>
<Input
type="text"

View File

@@ -1,12 +1,6 @@
import {
Button,
Dropdown,
DropdownTrigger,
DropdownMenu,
DropdownItem,
} from "@nextui-org/react";
import { MoreVertical } from "lucide-react";
import { FolderType, FoldersMessages } from "@/types/folder";
import { Button, Dropdown, DropdownTrigger, DropdownMenu, DropdownItem } from '@nextui-org/react';
import { MoreVertical } from 'lucide-react';
import { FolderType, FoldersMessages } from '@/types/folder';
type Props = {
folder: FolderType;
@@ -15,12 +9,7 @@ type Props = {
messages: FoldersMessages;
};
export default function FolderEditMenu({
folder,
onEditClick,
onDeleteClick,
messages,
}: Props) {
export default function FolderEditMenu({ folder, onEditClick, onDeleteClick, messages }: Props) {
return (
<Dropdown>
<DropdownTrigger>
@@ -32,12 +21,7 @@ export default function FolderEditMenu({
<DropdownItem key="edit" onClick={() => onEditClick(folder)}>
{messages.editFolder}
</DropdownItem>
<DropdownItem
key="delete"
className="text-danger"
color="danger"
onClick={() => onDeleteClick(folder.id)}
>
<DropdownItem key="delete" className="text-danger" color="danger" onClick={() => onDeleteClick(folder.id)}>
{messages.deleteFolder}
</DropdownItem>
</DropdownMenu>

View File

@@ -1,20 +1,15 @@
"use client";
import React from "react";
import { FolderType, FoldersMessages } from "@/types/folder";
import { useEffect, useState } from "react";
import { Button, Listbox, ListboxItem } from "@nextui-org/react";
import { Folder, Plus } from "lucide-react";
import { usePathname, useRouter } from "@/src/navigation";
import useGetCurrentIds from "@/utils/useGetCurrentIds";
import FolderDialog from "./FolderDialog";
import FolderEditMenu from "./FolderEditMenu";
'use client';
import React from 'react';
import { FolderType, FoldersMessages } from '@/types/folder';
import { useEffect, useState } from 'react';
import { Button, Listbox, ListboxItem } from '@nextui-org/react';
import { Folder, Plus } from 'lucide-react';
import { usePathname, useRouter } from '@/src/navigation';
import useGetCurrentIds from '@/utils/useGetCurrentIds';
import FolderDialog from './FolderDialog';
import FolderEditMenu from './FolderEditMenu';
import {
fetchFolders,
createFolder,
updateFolder,
deleteFolder,
} from "./foldersControl";
import { fetchFolders, createFolder, updateFolder, deleteFolder } from './foldersControl';
type Props = {
projectId: string;
@@ -45,16 +40,8 @@ export default function FoldersPane({ projectId, messages, locale }: Props) {
const onSubmit = async (name: string, detail: string) => {
if (editingFolder) {
const updatedProject = await updateFolder(
editingFolder.id,
name,
detail,
projectId,
null
);
const updatedProjects = folders.map((project) =>
project.id === updatedProject.id ? updatedProject : project
);
const updatedProject = await updateFolder(editingFolder.id, name, detail, projectId, null);
const updatedProjects = folders.map((project) => (project.id === updatedProject.id ? updatedProject : project));
setFolders(updatedProjects);
} else {
const newProject = await createFolder(name, detail, projectId, null);
@@ -79,28 +66,23 @@ export default function FoldersPane({ projectId, messages, locale }: Props) {
const data = await fetchFolders(projectId);
setFolders(data);
const selectedFolderFromUrl = data.find(
(folder) => folder.id === folderId
);
const selectedFolderFromUrl = data.find((folder) => folder.id === folderId);
setSelectedFolder(selectedFolderFromUrl);
// Redirect to the smallest folder ID page if the path is "projects/[projectId]/folders
if (pathname === `/projects/${projectId}/folders`) {
const smallestFolderId = Math.min(...data.map((folder) => folder.id));
router.push(
`/projects/${projectId}/folders/${smallestFolderId}/cases`,
{ locale: locale }
);
router.push(`/projects/${projectId}/folders/${smallestFolderId}/cases`, { locale: locale });
}
} catch (error: any) {
console.error("Error in effect:", error.message);
console.error('Error in effect:', error.message);
}
}
fetchDataEffect();
}, [folderId]);
const baseClass = "";
const baseClass = '';
const selectedClass = `${baseClass} bg-neutral-200 dark:bg-neutral-700`;
return (
@@ -119,18 +101,9 @@ export default function FoldersPane({ projectId, messages, locale }: Props) {
{folders.map((folder, index) => (
<ListboxItem
key={index}
onClick={() =>
router.push(
`/projects/${projectId}/folders/${folder.id}/cases`,
{ locale: locale }
)
}
onClick={() => router.push(`/projects/${projectId}/folders/${folder.id}/cases`, { locale: locale })}
startContent={<Folder size={20} color="#F7C24E" fill="#F7C24E" />}
className={
selectedFolder && folder.id === selectedFolder.id
? selectedClass
: baseClass
}
className={selectedFolder && folder.id === selectedFolder.id ? selectedClass : baseClass}
endContent={
<FolderEditMenu
folder={folder}

View File

@@ -1,8 +1,8 @@
"use client";
import { useEffect, useState } from "react";
import TestCaseTable from "./TestCaseTable";
import { fetchCases, createCase, deleteCase, deleteCases } from "./caseControl";
import { CasesMessages } from "@/types/case";
'use client';
import { useEffect, useState } from 'react';
import TestCaseTable from './TestCaseTable';
import { fetchCases, createCase, deleteCase, deleteCases } from './caseControl';
import { CasesMessages } from '@/types/case';
type Props = {
projectId: string;
@@ -11,12 +11,7 @@ type Props = {
locale: string;
};
export default function CasesPane({
projectId,
folderId,
messages,
locale,
}: Props) {
export default function CasesPane({ projectId, folderId, messages, locale }: Props) {
const [cases, setCases] = useState([]);
useEffect(() => {
async function fetchDataEffect() {
@@ -24,7 +19,7 @@ export default function CasesPane({
const data = await fetchCases(folderId);
setCases(data);
} catch (error: any) {
console.error("Error in effect:", error.message);
console.error('Error in effect:', error.message);
}
}

View File

@@ -1,4 +1,4 @@
import { useState, useMemo, useCallback } from "react";
import { useState, useMemo, useCallback } from 'react';
import {
Table,
TableHeader,
@@ -13,11 +13,11 @@ import {
DropdownItem,
Selection,
SortDescriptor,
} from "@nextui-org/react";
import { Link, NextUiLinkClasses } from "@/src/navigation";
import { Plus, MoreVertical, Trash, Circle } from "lucide-react";
import { CasesMessages } from "@/types/case";
import { priorities } from "@/config/selection";
} from '@nextui-org/react';
import { Link, NextUiLinkClasses } from '@/src/navigation';
import { Plus, MoreVertical, Trash, Circle } from 'lucide-react';
import { CasesMessages } from '@/types/case';
import { priorities } from '@/config/selection';
type Case = {
id: number;
@@ -55,16 +55,16 @@ export default function TestCaseTable({
locale,
}: Props) {
const headerColumns = [
{ name: messages.id, uid: "id", sortable: true },
{ name: messages.title, uid: "title", sortable: true },
{ name: messages.priority, uid: "priority", sortable: true },
{ name: messages.actions, uid: "actions" },
{ name: messages.id, uid: 'id', sortable: true },
{ name: messages.title, uid: 'title', sortable: true },
{ name: messages.priority, uid: 'priority', sortable: true },
{ name: messages.actions, uid: 'actions' },
];
const [selectedKeys, setSelectedKeys] = useState<Selection>(new Set([]));
const [sortDescriptor, setSortDescriptor] = useState<SortDescriptor>({
column: "id",
direction: "ascending",
column: 'id',
direction: 'ascending',
});
const sortedItems = useMemo(() => {
@@ -73,7 +73,7 @@ export default function TestCaseTable({
const second = b[sortDescriptor.column as keyof Case] as number;
const cmp = first < second ? -1 : first > second ? 1 : 0;
return sortDescriptor.direction === "descending" ? -cmp : cmp;
return sortDescriptor.direction === 'descending' ? -cmp : cmp;
});
}, [sortDescriptor, cases]);
@@ -81,15 +81,11 @@ export default function TestCaseTable({
const cellValue = testCase[columnKey as keyof Case];
switch (columnKey) {
case "id":
case 'id':
return <span>{cellValue}</span>;
case "title":
case 'title':
return (
<Button
size="sm"
variant="light"
className="data-[hover=true]:bg-transparent"
>
<Button size="sm" variant="light" className="data-[hover=true]:bg-transparent">
<Link
href={`/projects/${projectId}/folders/${testCase.folderId}/cases/${testCase.id}`}
locale={locale}
@@ -99,18 +95,14 @@ export default function TestCaseTable({
</Link>
</Button>
);
case "priority":
case 'priority':
return (
<div className="flex items-center">
<Circle
size={8}
color={priorities[cellValue].color}
fill={priorities[cellValue].color}
/>
<Circle size={8} color={priorities[cellValue].color} fill={priorities[cellValue].color} />
<div className="ms-3">{messages[priorities[cellValue].uid]}</div>
</div>
);
case "actions":
case 'actions':
return (
<Dropdown>
<DropdownTrigger>
@@ -119,10 +111,7 @@ export default function TestCaseTable({
</Button>
</DropdownTrigger>
<DropdownMenu aria-label="test case actions">
<DropdownItem
className="text-danger"
onClick={() => onDeleteCase(testCase.id)}
>
<DropdownItem className="text-danger" onClick={() => onDeleteCase(testCase.id)}>
{messages.deleteCase}
</DropdownItem>
</DropdownMenu>
@@ -135,25 +124,25 @@ export default function TestCaseTable({
const classNames = useMemo(
() => ({
wrapper: ["max-w-3xl"],
th: ["bg-transparent", "text-default-500", "border-b", "border-divider"],
wrapper: ['max-w-3xl'],
th: ['bg-transparent', 'text-default-500', 'border-b', 'border-divider'],
td: [
// changing the rows border radius
// first
"group-data-[first=true]:first:before:rounded-none",
"group-data-[first=true]:last:before:rounded-none",
'group-data-[first=true]:first:before:rounded-none',
'group-data-[first=true]:last:before:rounded-none',
// middle
"group-data-[middle=true]:before:rounded-none",
'group-data-[middle=true]:before:rounded-none',
// last
"group-data-[last=true]:first:before:rounded-none",
"group-data-[last=true]:last:before:rounded-none",
'group-data-[last=true]:first:before:rounded-none',
'group-data-[last=true]:last:before:rounded-none',
],
}),
[]
);
const onDeleteCasesClick = async () => {
if (selectedKeys === "all") {
if (selectedKeys === 'all') {
const allKeys = sortedItems.map((item) => item.id);
onDeleteCases(allKeys);
} else {
@@ -167,7 +156,7 @@ export default function TestCaseTable({
<div className="border-b-1 dark:border-neutral-700 w-full p-3 flex items-center justify-between">
<h3 className="font-bold">{messages.testCaseList}</h3>
<div>
{(selectedKeys.size > 0 || selectedKeys === "all") && (
{(selectedKeys.size > 0 || selectedKeys === 'all') && (
<Button
startContent={<Trash size={16} />}
size="sm"
@@ -178,12 +167,7 @@ export default function TestCaseTable({
{messages.delete}
</Button>
)}
<Button
startContent={<Plus size={16} />}
size="sm"
color="primary"
onClick={onCreateCase}
>
<Button startContent={<Plus size={16} />} size="sm" color="primary" onClick={onCreateCase}>
{messages.newTestCase}
</Button>
</div>
@@ -204,7 +188,7 @@ export default function TestCaseTable({
{(column) => (
<TableColumn
key={column.uid}
align={column.uid === "actions" ? "center" : "start"}
align={column.uid === 'actions' ? 'center' : 'start'}
allowsSorting={column.sortable}
>
{column.name}
@@ -213,11 +197,7 @@ export default function TestCaseTable({
</TableHeader>
<TableBody emptyContent={messages.noCasesFound} items={sortedItems}>
{(item) => (
<TableRow key={item.id}>
{(columnKey) => (
<TableCell>{renderCell(item, columnKey)}</TableCell>
)}
</TableRow>
<TableRow key={item.id}>{(columnKey) => <TableCell>{renderCell(item, columnKey)}</TableCell>}</TableRow>
)}
</TableBody>
</Table>

View File

@@ -1,16 +1,13 @@
import { Image, Button, Tooltip, Card, CardBody } from "@nextui-org/react";
import { AttachmentType, CaseMessages } from "@/types/case";
import { Trash, ArrowDownToLine } from "lucide-react";
import { isImage } from "./isImage";
import { Image, Button, Tooltip, Card, CardBody } from '@nextui-org/react';
import { AttachmentType, CaseMessages } from '@/types/case';
import { Trash, ArrowDownToLine } from 'lucide-react';
import { isImage } from './isImage';
type Props = {
attachments: AttachmentType[];
onAttachmentDownload: (
attachmentId: number,
downloadFileName: string
) => void;
onAttachmentDownload: (attachmentId: number, downloadFileName: string) => void;
onAttachmentDelete: (attachmentId: number) => void;
messages: CaseMessages,
messages: CaseMessages;
};
export default function CaseAttachmentsEditor({
@@ -35,11 +32,7 @@ export default function CaseAttachmentsEditor({
{images.map((image, index) => (
<Card key={index} radius="sm" className="mt-2 me-2 max-w-md">
<CardBody>
<Image
alt={image.title}
src={image.path}
className="object-cover h-40 w-40"
/>
<Image alt={image.title} src={image.path} className="object-cover h-40 w-40" />
<div className="flex items-center justify-between">
<p>{image.title}</p>
<Tooltip content={messages.delete}>

View File

@@ -1,39 +1,27 @@
"use client";
import { useEffect, useState } from "react";
import {
Input,
Textarea,
Select,
SelectItem,
Button,
Divider,
Tooltip,
} from "@nextui-org/react";
import { useRouter } from "@/src/navigation";
import { Save, Plus, ArrowLeft, ArrowUpFromLine, Circle } from "lucide-react";
import { priorities, testTypes, templates } from "@/config/selection";
import CaseStepsEditor from "./CaseStepsEditor";
import CaseAttachmentsEditor from "./CaseAttachmentsEditor";
import { CaseType, AttachmentType, CaseMessages } from "@/types/case";
import { fetchCase, updateCase } from "../caseControl";
import { fetchCreateStep, fetchDeleteStep } from "./stepControl";
import {
fetchCreateAttachments,
fetchDownloadAttachment,
fetchDeleteAttachment,
} from "./attachmentControl";
'use client';
import { useEffect, useState } from 'react';
import { Input, Textarea, Select, SelectItem, Button, Divider, Tooltip } from '@nextui-org/react';
import { useRouter } from '@/src/navigation';
import { Save, Plus, ArrowLeft, ArrowUpFromLine, Circle } from 'lucide-react';
import { priorities, testTypes, templates } from '@/config/selection';
import CaseStepsEditor from './CaseStepsEditor';
import CaseAttachmentsEditor from './CaseAttachmentsEditor';
import { CaseType, AttachmentType, CaseMessages } from '@/types/case';
import { fetchCase, updateCase } from '../caseControl';
import { fetchCreateStep, fetchDeleteStep } from './stepControl';
import { fetchCreateAttachments, fetchDownloadAttachment, fetchDeleteAttachment } from './attachmentControl';
const defaultTestCase = {
id: 0,
title: "",
title: '',
state: 0,
priority: 0,
type: 0,
automationStatus: 0,
description: "",
description: '',
template: 0,
preConditions: "",
expectedResults: "",
preConditions: '',
expectedResults: '',
folderId: 0,
};
@@ -45,13 +33,7 @@ type Props = {
locale: string;
};
export default function CaseEditor({
projectId,
folderId,
caseId,
messages,
locale,
}: Props) {
export default function CaseEditor({ projectId, folderId, caseId, messages, locale }: Props) {
const [testCase, setTestCase] = useState<CaseType>(defaultTestCase);
const [isTitleInvalid, setIsTitleInvalid] = useState<boolean>(false);
const [isUpdating, setIsUpdating] = useState<boolean>(false);
@@ -122,10 +104,7 @@ export default function CaseEditor({
handleFetchCreateAttachments(caseId, event.target.files);
};
const handleFetchCreateAttachments = async (
caseId: number,
files: File[]
) => {
const handleFetchCreateAttachments = async (caseId: number, files: File[]) => {
const newAttachments = await fetchCreateAttachments(caseId, files);
if (newAttachments) {
@@ -147,9 +126,7 @@ export default function CaseEditor({
const onAttachmentDelete = async (attachmentId: number) => {
await fetchDeleteAttachment(attachmentId);
const filteredAttachments = testCase.Attachments.filter(
(attachment) => attachment.id !== attachmentId
);
const filteredAttachments = testCase.Attachments.filter((attachment) => attachment.id !== attachmentId);
setTestCase({
...testCase,
@@ -163,7 +140,7 @@ export default function CaseEditor({
const data = await fetchCase(caseId);
setTestCase(data);
} catch (error: any) {
console.error("Error in effect:", error.message);
console.error('Error in effect:', error.message);
}
}
@@ -179,12 +156,7 @@ export default function CaseEditor({
isIconOnly
size="sm"
className="rounded-full bg-neutral-50 dark:bg-neutral-600"
onPress={() =>
router.push(
`/projects/${projectId}/folders/${folderId}/cases`,
{ locale: locale }
)
}
onPress={() => router.push(`/projects/${projectId}/folders/${folderId}/cases`, { locale: locale })}
>
<ArrowLeft size={16} />
</Button>
@@ -215,7 +187,7 @@ export default function CaseEditor({
label={messages.title}
value={testCase.title}
isInvalid={isTitleInvalid}
errorMessage={isTitleInvalid ? messages.pleaseEnterTitle : ""}
errorMessage={isTitleInvalid ? messages.pleaseEnterTitle : ''}
onChange={(e) => {
setTestCase({ ...testCase, title: e.target.value });
}}
@@ -241,17 +213,11 @@ export default function CaseEditor({
selectedKeys={[priorities[testCase.priority].uid]}
onSelectionChange={(e) => {
const selectedUid = e.anchorKey;
const index = priorities.findIndex(
(priority) => priority.uid === selectedUid
);
const index = priorities.findIndex((priority) => priority.uid === selectedUid);
setTestCase({ ...testCase, priority: index });
}}
startContent={
<Circle
size={8}
color={priorities[testCase.priority].color}
fill={priorities[testCase.priority].color}
/>
<Circle size={8} color={priorities[testCase.priority].color} fill={priorities[testCase.priority].color} />
}
label={messages.priority}
className="mt-3 max-w-xs"
@@ -271,9 +237,7 @@ export default function CaseEditor({
selectedKeys={[testTypes[testCase.type].uid]}
onSelectionChange={(e) => {
const selectedUid = e.anchorKey;
const index = testTypes.findIndex(
(type) => type.uid === selectedUid
);
const index = testTypes.findIndex((type) => type.uid === selectedUid);
setTestCase({ ...testCase, type: index });
}}
label={messages.type}
@@ -294,9 +258,7 @@ export default function CaseEditor({
selectedKeys={[templates[testCase.template].uid]}
onSelectionChange={(e) => {
const selectedUid = e.anchorKey;
const index = templates.findIndex(
(template) => template.uid === selectedUid
);
const index = templates.findIndex((template) => template.uid === selectedUid);
setTestCase({ ...testCase, template: index });
}}
label={messages.template}
@@ -311,7 +273,7 @@ export default function CaseEditor({
</div>
<Divider className="my-6" />
{templates[testCase.template].uid === "text" ? (
{templates[testCase.template].uid === 'text' ? (
<div>
<h6 className="font-bold">{messages.testDetail}</h6>
<div className="flex">
@@ -377,10 +339,9 @@ export default function CaseEditor({
<h6 className="font-bold">{messages.attachments}</h6>
<CaseAttachmentsEditor
attachments={testCase.Attachments}
onAttachmentDownload={(
attachmentId: number,
downloadFileName: string
) => fetchDownloadAttachment(attachmentId, downloadFileName)}
onAttachmentDownload={(attachmentId: number, downloadFileName: string) =>
fetchDownloadAttachment(attachmentId, downloadFileName)
}
onAttachmentDelete={onAttachmentDelete}
messages={messages}
/>
@@ -399,17 +360,9 @@ export default function CaseEditor({
<span className="font-semibold">{messages.clickToUpload}</span>
<span>{messages.orDragAndDrop}</span>
</p>
<p className="text-xs text-neutral-500 dark:text-neutral-400">
{messages.maxFileSize}: 50 MB
</p>
<p className="text-xs text-neutral-500 dark:text-neutral-400">{messages.maxFileSize}: 50 MB</p>
</div>
<input
id="dropzone-file"
type="file"
className="hidden"
onChange={handleInput}
multiple
/>
<input id="dropzone-file" type="file" className="hidden" onChange={handleInput} multiple />
</label>
</div>
</div>

View File

@@ -1,6 +1,6 @@
import { Textarea, Button, Tooltip } from "@nextui-org/react";
import { CaseMessages, StepType } from "@/types/case";
import { Plus, Trash } from "lucide-react";
import { Textarea, Button, Tooltip } from '@nextui-org/react';
import { CaseMessages, StepType } from '@/types/case';
import { Plus, Trash } from 'lucide-react';
type Props = {
steps: StepType[];
@@ -10,13 +10,7 @@ type Props = {
messages: CaseMessages;
};
export default function StepsEditor({
steps,
onStepUpdate,
onStepPlus,
onStepDelete,
messages,
}: Props) {
export default function StepsEditor({ steps, onStepUpdate, onStepPlus, onStepDelete, messages }: Props) {
// sort steps by junction table's column
const sortedSteps = steps.slice().sort((a, b) => {
const stepNoA = a.caseSteps.stepNo;

View File

@@ -1,5 +1,5 @@
import CaseEditor from "./CaseEditor";
import { useTranslations } from "next-intl";
import CaseEditor from './CaseEditor';
import { useTranslations } from 'next-intl';
export default function Page({
params,
@@ -11,53 +11,53 @@ export default function Page({
locale: string;
};
}) {
const t = useTranslations("Case");
const t = useTranslations('Case');
const messages = {
backToCases: t("back_to_cases"),
updating: t("updating"),
update: t("update"),
basic: t("basic"),
title: t("title"),
pleaseEnterTitle: t("please_enter_title"),
description: t("description"),
testCaseDescription: t("test_case_description"),
priority: t("priority"),
critical: t("critical"),
high: t("high"),
medium: t("medium"),
low: t("low"),
type: t("type"),
other: t("other"),
security: t("security"),
performance: t("performance"),
accessibility: t("accessibility"),
functional: t("functional"),
acceptance: t("acceptance"),
usability: t("usability"),
smokeSanity: t("smoke_sanity"),
compatibility: t("compatibility"),
destructive: t("destructive"),
regression: t("regression"),
automated: t("automated"),
manual: t("manual"),
template: t("template"),
testDetail: t("test_detail"),
preconditions: t("preconditions"),
step: t("step"),
text: t("text"),
steps: t("steps"),
newStep: t("new_step"),
detailsOfTheStep: t("details_of_the_step"),
expectedResult: t("expected_result"),
deleteThisStep: t("delete_this_step"),
insertStep: t("insert_step"),
attachments: t("attachments"),
delete: t("delete"),
download: t("download"),
deleteFile: t("delete_file"),
clickToUpload: t("click_to_upload"),
orDragAndDrop: t("or_drag_and_drop"),
maxFileSize: t("max_file_size"),
backToCases: t('back_to_cases'),
updating: t('updating'),
update: t('update'),
basic: t('basic'),
title: t('title'),
pleaseEnterTitle: t('please_enter_title'),
description: t('description'),
testCaseDescription: t('test_case_description'),
priority: t('priority'),
critical: t('critical'),
high: t('high'),
medium: t('medium'),
low: t('low'),
type: t('type'),
other: t('other'),
security: t('security'),
performance: t('performance'),
accessibility: t('accessibility'),
functional: t('functional'),
acceptance: t('acceptance'),
usability: t('usability'),
smokeSanity: t('smoke_sanity'),
compatibility: t('compatibility'),
destructive: t('destructive'),
regression: t('regression'),
automated: t('automated'),
manual: t('manual'),
template: t('template'),
testDetail: t('test_detail'),
preconditions: t('preconditions'),
step: t('step'),
text: t('text'),
steps: t('steps'),
newStep: t('new_step'),
detailsOfTheStep: t('details_of_the_step'),
expectedResult: t('expected_result'),
deleteThisStep: t('delete_this_step'),
insertStep: t('insert_step'),
attachments: t('attachments'),
delete: t('delete'),
download: t('download'),
deleteFile: t('delete_file'),
clickToUpload: t('click_to_upload'),
orDragAndDrop: t('or_drag_and_drop'),
maxFileSize: t('max_file_size'),
};
return (

View File

@@ -1,37 +1,28 @@
import CasesPane from "./CasesPane";
import { useTranslations } from "next-intl";
import CasesPane from './CasesPane';
import { useTranslations } from 'next-intl';
export default function Page({
params,
}: {
params: { projectId: string; folderId: string; locale: string };
}) {
const t = useTranslations("Cases");
export default function Page({ params }: { params: { projectId: string; folderId: string; locale: string } }) {
const t = useTranslations('Cases');
const messages = {
testCaseList: t("test_case_list"),
id: t("id"),
title: t("title"),
priority: t("priority"),
actions: t("actions"),
deleteCase: t("delete_case"),
delete: t("delete"),
newTestCase: t("new_test_case"),
status: t("status"),
critical: t("critical"),
high: t("high"),
medium: t("medium"),
low: t("low"),
noCasesFound: t("no_cases_found"),
testCaseList: t('test_case_list'),
id: t('id'),
title: t('title'),
priority: t('priority'),
actions: t('actions'),
deleteCase: t('delete_case'),
delete: t('delete'),
newTestCase: t('new_test_case'),
status: t('status'),
critical: t('critical'),
high: t('high'),
medium: t('medium'),
low: t('low'),
noCasesFound: t('no_cases_found'),
};
return (
<>
<CasesPane
projectId={params.projectId}
folderId={params.folderId}
locale={params.locale}
messages={messages}
/>
<CasesPane projectId={params.projectId} folderId={params.folderId} locale={params.locale} messages={messages} />
</>
);
}

View File

@@ -1,5 +1,5 @@
import FoldersPane from "./FoldersPane";
import { useTranslations } from "next-intl";
import FoldersPane from './FoldersPane';
import { useTranslations } from 'next-intl';
export default function FoldersLayout({
children,
@@ -8,27 +8,23 @@ export default function FoldersLayout({
children: React.ReactNode;
params: { projectId: string; locale: string };
}) {
const t = useTranslations("Folders");
const t = useTranslations('Folders');
const messages = {
folder: t("folder"),
newFolder: t("new_folder"),
editFolder: t("edit_folder"),
deleteFolder: t("delete_folder"),
folderName: t("folder_name"),
folderDetail: t("folder_detail"),
close: t("close"),
create: t("create"),
update: t("update"),
pleaseEnter: t("please_enter"),
folder: t('folder'),
newFolder: t('new_folder'),
editFolder: t('edit_folder'),
deleteFolder: t('delete_folder'),
folderName: t('folder_name'),
folderDetail: t('folder_detail'),
close: t('close'),
create: t('create'),
update: t('update'),
pleaseEnter: t('please_enter'),
};
return (
<div className="flex w-full">
<FoldersPane
projectId={params.projectId}
messages={messages}
locale={params.locale}
/>
<FoldersPane projectId={params.projectId} messages={messages} locale={params.locale} />
<div className="flex-grow w-full">{children}</div>
</div>
);

View File

@@ -1,7 +1,3 @@
export default function Page() {
return (
<>
This is folders page.
</>
);
}
return <>This is folders page.</>;
}

View File

@@ -1,10 +1,10 @@
import React from "react";
import { useState, useEffect } from "react";
import dynamic from "next/dynamic";
import { priorities } from "@/config/selection";
import { CasePriorityCountType } from "@/types/case";
import { HomeMessages } from "./page";
const Chart = dynamic(() => import("react-apexcharts"), { ssr: false });
import React from 'react';
import { useState, useEffect } from 'react';
import dynamic from 'next/dynamic';
import { priorities } from '@/config/selection';
import { CasePriorityCountType } from '@/types/case';
import { HomeMessages } from './page';
const Chart = dynamic(() => import('react-apexcharts'), { ssr: false });
type Props = {
priorityCounts: CasePriorityCountType[];
@@ -12,11 +12,7 @@ type Props = {
theme: string;
};
export default function TestPriorityDonutChart({
priorityCounts,
messages,
theme,
}: Props) {
export default function TestPriorityDonutChart({ priorityCounts, messages, theme }: Props) {
const [chartData, setChartData] = useState({
series: [],
options: {
@@ -38,10 +34,10 @@ export default function TestPriorityDonutChart({
const legend = {
labels: {
colors: priorities.map((entry) => {
if (theme === "light") {
return "black";
if (theme === 'light') {
return 'black';
} else {
return "white";
return 'white';
}
}),
},
@@ -57,13 +53,5 @@ export default function TestPriorityDonutChart({
updateChartDate();
}, [priorityCounts, theme]);
return (
<Chart
options={chartData.options}
series={chartData.series}
type="donut"
width={"100%"}
height={"100%"}
/>
);
return <Chart options={chartData.options} series={chartData.series} type="donut" width={'100%'} height={'100%'} />;
}

View File

@@ -1,9 +1,9 @@
import React from "react";
import { useState, useEffect } from "react";
import dynamic from "next/dynamic";
import { ProgressSeriesType } from "@/types/run";
import { testRunCaseStatus } from "@/config/selection";
const Chart = dynamic(() => import("react-apexcharts"), { ssr: false });
import React from 'react';
import { useState, useEffect } from 'react';
import dynamic from 'next/dynamic';
import { ProgressSeriesType } from '@/types/run';
import { testRunCaseStatus } from '@/config/selection';
const Chart = dynamic(() => import('react-apexcharts'), { ssr: false });
type Props = {
progressSeries: ProgressSeriesType[];
@@ -11,11 +11,7 @@ type Props = {
theme: string;
};
export default function TestProgressBarChart({
progressSeries,
progressCategories,
theme,
}: Props) {
export default function TestProgressBarChart({ progressSeries, progressCategories, theme }: Props) {
const [chartData, setChartData] = useState({
series: [],
options: {
@@ -28,14 +24,14 @@ export default function TestProgressBarChart({
const updateChartDate = () => {
if (progressSeries) {
const legendsLabelColors = testRunCaseStatus.map((itr) => {
if (theme === "light") {
return "black";
if (theme === 'light') {
return 'black';
} else {
return "white";
return 'white';
}
});
const xaxisLabelColor = theme === "light" ? "black" : "white";
const xaxisLabelColor = theme === 'light' ? 'black' : 'white';
setChartData({
series: progressSeries,
@@ -47,7 +43,7 @@ export default function TestProgressBarChart({
stacked: true,
},
legend: {
position: "right",
position: 'right',
labels: {
colors: legendsLabelColors,
},
@@ -56,7 +52,7 @@ export default function TestProgressBarChart({
return itr.chartColor;
}),
xaxis: {
type: "datetime",
type: 'datetime',
categories: progressCategories,
labels: {
style: {
@@ -75,13 +71,5 @@ export default function TestProgressBarChart({
updateChartDate();
}, [progressSeries, progressCategories, theme]);
return (
<Chart
options={chartData.options}
series={chartData.series}
type="bar"
width={"100%"}
height={"100%"}
/>
);
return <Chart options={chartData.options} series={chartData.series} type="bar" width={'100%'} height={'100%'} />;
}

View File

@@ -1,10 +1,10 @@
import React from "react";
import { useState, useEffect } from "react";
import dynamic from "next/dynamic";
import { testTypes } from "@/config/selection";
import { CaseTypeCountType } from "@/types/case";
import { HomeMessages } from "./page";
const Chart = dynamic(() => import("react-apexcharts"), { ssr: false });
import React from 'react';
import { useState, useEffect } from 'react';
import dynamic from 'next/dynamic';
import { testTypes } from '@/config/selection';
import { CaseTypeCountType } from '@/types/case';
import { HomeMessages } from './page';
const Chart = dynamic(() => import('react-apexcharts'), { ssr: false });
type Props = {
typesCounts: CaseTypeCountType[];
@@ -12,11 +12,7 @@ type Props = {
theme: string;
};
export default function TestTypesDonutChart({
typesCounts,
messages,
theme,
}: Props) {
export default function TestTypesDonutChart({ typesCounts, messages, theme }: Props) {
const [chartData, setChartData] = useState({
series: [],
options: {
@@ -38,10 +34,10 @@ export default function TestTypesDonutChart({
const legend = {
labels: {
colors: testTypes.map((entry) => {
if (theme === "light") {
return "black";
if (theme === 'light') {
return 'black';
} else {
return "white";
return 'white';
}
}),
},
@@ -57,13 +53,5 @@ export default function TestTypesDonutChart({
updateChartDate();
}, [typesCounts, theme]);
return (
<Chart
options={chartData.options}
series={chartData.series}
type="donut"
width={"100%"}
height={"100%"}
/>
);
return <Chart options={chartData.options} series={chartData.series} type="donut" width={'100%'} height={'100%'} />;
}

View File

@@ -1,31 +1,26 @@
"use client";
import { useState, useEffect } from "react";
import { Divider } from "@nextui-org/react";
import { title, subtitle } from "@/components/primitives";
import { Card, CardBody, Chip } from "@nextui-org/react";
import { Folder, Clipboard, FlaskConical } from "lucide-react";
import { CaseTypeCountType, CasePriorityCountType } from "@/types/case";
import { ProgressSeriesType } from "@/types/run";
import { HomeMessages } from "./page";
import {
aggregateBasicInfo,
aggregateTestPriority,
aggregateTestType,
aggregateProgress,
} from "./aggregate";
import TestTypesChart from "./TestTypesDonutChart";
import TestPriorityChart from "./TestPriorityDonutChart";
import TestProgressBarChart from "./TestProgressColumnChart";
import Config from "@/config/config";
import { useTheme } from "next-themes";
'use client';
import { useState, useEffect } from 'react';
import { Divider } from '@nextui-org/react';
import { title, subtitle } from '@/components/primitives';
import { Card, CardBody, Chip } from '@nextui-org/react';
import { Folder, Clipboard, FlaskConical } from 'lucide-react';
import { CaseTypeCountType, CasePriorityCountType } from '@/types/case';
import { ProgressSeriesType } from '@/types/run';
import { HomeMessages } from './page';
import { aggregateBasicInfo, aggregateTestPriority, aggregateTestType, aggregateProgress } from './aggregate';
import TestTypesChart from './TestTypesDonutChart';
import TestPriorityChart from './TestPriorityDonutChart';
import TestProgressBarChart from './TestProgressColumnChart';
import Config from '@/config/config';
import { useTheme } from 'next-themes';
const apiServer = Config.apiServer;
async function fetchProject(url) {
try {
const response = await fetch(url, {
method: "GET",
method: 'GET',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
});
@@ -36,7 +31,7 @@ async function fetchProject(url) {
const data = await response.json();
return data;
} catch (error: any) {
console.error("Error fetching data:", error.message);
console.error('Error fetching data:', error.message);
}
}
@@ -48,8 +43,8 @@ type Props = {
export function Home({ projectId, messages }: Props) {
const { theme, setTheme } = useTheme();
const [project, setProject] = useState({
name: "",
detail: "",
name: '',
detail: '',
Folders: [{ Cases: [] }],
Runs: [{ RunCases: [] }],
});
@@ -57,8 +52,7 @@ export function Home({ projectId, messages }: Props) {
const [caseNum, setCaseNum] = useState(0);
const [runNum, setRunNum] = useState(0);
const [typesCounts, setTypesCounts] = useState<CaseTypeCountType[]>();
const [priorityCounts, setPriorityCounts] =
useState<CasePriorityCountType[]>();
const [priorityCounts, setPriorityCounts] = useState<CasePriorityCountType[]>();
const [progressCategories, setProgressCategories] = useState<string[]>();
const [progressSeries, setProgressSeries] = useState<ProgressSeriesType[]>();
const url = `${apiServer}/home/${projectId}`;
@@ -69,7 +63,7 @@ export function Home({ projectId, messages }: Props) {
const data = await fetchProject(url);
setProject(data);
} catch (error: any) {
console.error("Error in effect:", error.message);
console.error('Error in effect:', error.message);
}
}
@@ -99,68 +93,41 @@ export function Home({ projectId, messages }: Props) {
return (
<div className="container mx-auto max-w-5xl pt-6 px-6 flex-grow">
<h1 className={title({ size: "sm" })}>{project.name}</h1>
<h1 className={title({ size: 'sm' })}>{project.name}</h1>
<div className="mt-4">
<Chip
variant="flat"
startContent={<Folder size={16} />}
className="px-3"
>
<Chip variant="flat" startContent={<Folder size={16} />} className="px-3">
{folderNum} {messages.folders}
</Chip>
<Chip
variant="flat"
startContent={<Clipboard size={16} />}
className="px-3 ms-2"
>
<Chip variant="flat" startContent={<Clipboard size={16} />} className="px-3 ms-2">
{caseNum} {messages.testCases}
</Chip>
<Chip
variant="flat"
startContent={<FlaskConical size={16} />}
className="px-3 ms-2"
>
<Chip variant="flat" startContent={<FlaskConical size={16} />} className="px-3 ms-2">
{runNum} {messages.testRuns}
</Chip>
</div>
{project.detail && (
<Card
className="mt-3 bg-neutral-100 dark:bg-neutral-700 dark:text-white"
shadow="none"
>
<Card className="mt-3 bg-neutral-100 dark:bg-neutral-700 dark:text-white" shadow="none">
<CardBody>{project.detail}</CardBody>
</Card>
)}
<Divider className="my-8" />
<h2 className={subtitle()}>{messages.progress}</h2>
<div style={{ height: "18rem" }}>
<TestProgressBarChart
progressSeries={progressSeries}
progressCategories={progressCategories}
theme={theme}
/>
<div style={{ height: '18rem' }}>
<TestProgressBarChart progressSeries={progressSeries} progressCategories={progressCategories} theme={theme} />
</div>
<Divider className="my-12" />
<h2 className={subtitle()}>{messages.testClassification}</h2>
<div className="flex pb-20">
<div style={{ width: "32rem", height: "18rem" }}>
<div style={{ width: '32rem', height: '18rem' }}>
<h3>{messages.byType}</h3>
<TestTypesChart
typesCounts={typesCounts}
messages={messages}
theme={theme}
/>
<TestTypesChart typesCounts={typesCounts} messages={messages} theme={theme} />
</div>
<div style={{ width: "30rem", height: "18rem" }}>
<div style={{ width: '30rem', height: '18rem' }}>
<h3>{messages.byPriority}</h3>
<TestPriorityChart
priorityCounts={priorityCounts}
messages={messages}
theme={theme}
/>
<TestPriorityChart priorityCounts={priorityCounts} messages={messages} theme={theme} />
</div>
</div>
</div>

View File

@@ -1,5 +1,5 @@
import { Home } from "./home";
import { useTranslations } from "next-intl";
import { Home } from './home';
import { useTranslations } from 'next-intl';
export type HomeMessages = {
folders: string;
@@ -35,37 +35,37 @@ export type HomeMessages = {
};
export default function Page({ params }: { params: { projectId: string } }) {
const t = useTranslations("Home");
const t = useTranslations('Home');
const messages = {
folders: t("Folders"),
testCases: t("test_cases"),
testRuns: t("test_runs"),
progress: t("progress"),
untested: t("untested"),
passed: t("passed"),
failed: t("failed"),
retest: t("retest"),
skipped: t("skipped"),
testClassification: t("test_classification"),
byType: t("by_type"),
byPriority: t("by_priority"),
other: t("other"),
security: t("security"),
performance: t("performance"),
accessibility: t("accessibility"),
functional: t("functional"),
acceptance: t("acceptance"),
usability: t("usability"),
smokeSanity: t("smoke_sanity"),
compatibility: t("compatibility"),
destructive: t("destructive"),
regression: t("regression"),
automated: t("automated"),
manual: t("manual"),
critical: t("critical"),
high: t("high"),
medium: t("medium"),
low: t("low"),
folders: t('Folders'),
testCases: t('test_cases'),
testRuns: t('test_runs'),
progress: t('progress'),
untested: t('untested'),
passed: t('passed'),
failed: t('failed'),
retest: t('retest'),
skipped: t('skipped'),
testClassification: t('test_classification'),
byType: t('by_type'),
byPriority: t('by_priority'),
other: t('other'),
security: t('security'),
performance: t('performance'),
accessibility: t('accessibility'),
functional: t('functional'),
acceptance: t('acceptance'),
usability: t('usability'),
smokeSanity: t('smoke_sanity'),
compatibility: t('compatibility'),
destructive: t('destructive'),
regression: t('regression'),
automated: t('automated'),
manual: t('manual'),
critical: t('critical'),
high: t('high'),
medium: t('medium'),
low: t('low'),
};
return (
<>

View File

@@ -1,5 +1,5 @@
import Sidebar from "./Sidebar";
import { useTranslations } from "next-intl";
import Sidebar from './Sidebar';
import { useTranslations } from 'next-intl';
export default function SidebarLayout({
children,
@@ -8,11 +8,11 @@ export default function SidebarLayout({
children: React.ReactNode;
params: { locale: string };
}) {
const t = useTranslations("Project");
const t = useTranslations('Project');
const messages = {
home: t("home"),
testCases: t("test_cases"),
testRuns: t("test_runs"),
home: t('home'),
testCases: t('test_cases'),
testRuns: t('test_runs'),
};
return (

View File

@@ -1,10 +1,10 @@
"use client";
import { useEffect, useState } from "react";
import { Button } from "@nextui-org/react";
import { Plus } from "lucide-react";
import RunsTable from "./RunsTable";
import { fetchRuns, createRun, deleteRun } from "./runsControl";
import { RunsMessages } from "@/types/run";
'use client';
import { useEffect, useState } from 'react';
import { Button } from '@nextui-org/react';
import { Plus } from 'lucide-react';
import RunsTable from './RunsTable';
import { fetchRuns, createRun, deleteRun } from './runsControl';
import { RunsMessages } from '@/types/run';
type Props = {
projectId: string;
@@ -21,7 +21,7 @@ export default function RunsPage({ projectId, locale, messages }: Props) {
const data = await fetchRuns(projectId);
setRuns(data);
} catch (error: any) {
console.error("Error in effect:", error.message);
console.error('Error in effect:', error.message);
}
}
@@ -35,7 +35,7 @@ export default function RunsPage({ projectId, locale, messages }: Props) {
updateRuns.push(newRun);
setRuns(updateRuns);
} catch (error: any) {
console.error("Error deleting run:", error);
console.error('Error deleting run:', error);
}
};
@@ -45,7 +45,7 @@ export default function RunsPage({ projectId, locale, messages }: Props) {
const data = await fetchRuns(projectId);
setRuns(data);
} catch (error: any) {
console.error("Error deleting run:", error);
console.error('Error deleting run:', error);
}
};
@@ -54,24 +54,13 @@ export default function RunsPage({ projectId, locale, messages }: Props) {
<div className="w-full p-3 flex items-center justify-between">
<h3 className="font-bold">{messages.runList}</h3>
<div>
<Button
startContent={<Plus size={16} />}
size="sm"
color="primary"
onClick={onCreateClick}
>
<Button startContent={<Plus size={16} />} size="sm" color="primary" onClick={onCreateClick}>
{messages.newRun}
</Button>
</div>
</div>
<RunsTable
projectId={projectId}
runs={runs}
onDeleteRun={onDeleteClick}
messages={messages}
locale={locale}
/>
<RunsTable projectId={projectId} runs={runs} onDeleteRun={onDeleteClick} messages={messages} locale={locale} />
</div>
);
}

View File

@@ -1,4 +1,4 @@
import { useState, useMemo, useCallback } from "react";
import { useState, useMemo, useCallback } from 'react';
import {
Table,
TableHeader,
@@ -12,11 +12,11 @@ import {
DropdownMenu,
DropdownItem,
SortDescriptor,
} from "@nextui-org/react";
import { Link, NextUiLinkClasses } from "@/src/navigation";
import { MoreVertical } from "lucide-react";
import { RunsMessages, RunType } from "@/types/run";
import dayjs from "dayjs";
} from '@nextui-org/react';
import { Link, NextUiLinkClasses } from '@/src/navigation';
import { MoreVertical } from 'lucide-react';
import { RunsMessages, RunType } from '@/types/run';
import dayjs from 'dayjs';
type Props = {
projectId: string;
@@ -26,24 +26,18 @@ type Props = {
locale: string;
};
export default function RunsTable({
projectId,
runs,
onDeleteRun,
messages,
locale,
}: Props) {
export default function RunsTable({ projectId, runs, onDeleteRun, messages, locale }: Props) {
const headerColumns = [
{ name: messages.id, uid: "id", sortable: true },
{ name: messages.name, uid: "name", sortable: true },
{ name: messages.description, uid: "description", sortable: true },
{ name: messages.lastUpdate, uid: "updatedAt", sortable: true },
{ name: messages.actions, uid: "actions" },
{ name: messages.id, uid: 'id', sortable: true },
{ name: messages.name, uid: 'name', sortable: true },
{ name: messages.description, uid: 'description', sortable: true },
{ name: messages.lastUpdate, uid: 'updatedAt', sortable: true },
{ name: messages.actions, uid: 'actions' },
];
const [sortDescriptor, setSortDescriptor] = useState<SortDescriptor>({
column: "id",
direction: "ascending",
column: 'id',
direction: 'ascending',
});
const sortedItems = useMemo(() => {
@@ -52,31 +46,27 @@ export default function RunsTable({
const second = b[sortDescriptor.column as keyof RunType] as number;
const cmp = first < second ? -1 : first > second ? 1 : 0;
return sortDescriptor.direction === "descending" ? -cmp : cmp;
return sortDescriptor.direction === 'descending' ? -cmp : cmp;
});
}, [sortDescriptor, runs]);
const truncateText = (text: string, maxLength: number) => {
return text.length > maxLength ? text.slice(0, maxLength) + "..." : text;
return text.length > maxLength ? text.slice(0, maxLength) + '...' : text;
};
const renderCell = useCallback((run: RunType, columnKey: Key) => {
const cellValue = run[columnKey as keyof RunType];
switch (columnKey) {
case "id":
case 'id':
return <span>{cellValue}</span>;
case "name":
case 'name':
return (
<Link
href={`/projects/${projectId}/runs/${run.id}`}
locale={locale}
className={NextUiLinkClasses}
>
<Link href={`/projects/${projectId}/runs/${run.id}`} locale={locale} className={NextUiLinkClasses}>
{cellValue}
</Link>
);
case "detail":
case 'detail':
const maxLength = 20;
const truncatedValue = truncateText(cellValue, maxLength);
return (
@@ -84,9 +74,9 @@ export default function RunsTable({
<div>{truncatedValue}</div>
</div>
);
case "updatedAt":
return <span>{dayjs(cellValue).format("YYYY/MM/DD HH:mm")}</span>;
case "actions":
case 'updatedAt':
return <span>{dayjs(cellValue).format('YYYY/MM/DD HH:mm')}</span>;
case 'actions':
return (
<Dropdown>
<DropdownTrigger>
@@ -95,10 +85,7 @@ export default function RunsTable({
</Button>
</DropdownTrigger>
<DropdownMenu aria-label="run actions">
<DropdownItem
className="text-danger"
onClick={() => onDeleteRun(run.id)}
>
<DropdownItem className="text-danger" onClick={() => onDeleteRun(run.id)}>
{messages.deleteRun}
</DropdownItem>
</DropdownMenu>
@@ -111,18 +98,18 @@ export default function RunsTable({
const classNames = useMemo(
() => ({
wrapper: ["max-w-3xl"],
th: ["bg-transparent", "text-default-500", "border-b", "border-divider"],
wrapper: ['max-w-3xl'],
th: ['bg-transparent', 'text-default-500', 'border-b', 'border-divider'],
td: [
// changing the rows border radius
// first
"group-data-[first=true]:first:before:rounded-none",
"group-data-[first=true]:last:before:rounded-none",
'group-data-[first=true]:first:before:rounded-none',
'group-data-[first=true]:last:before:rounded-none',
// middle
"group-data-[middle=true]:before:rounded-none",
'group-data-[middle=true]:before:rounded-none',
// last
"group-data-[last=true]:first:before:rounded-none",
"group-data-[last=true]:last:before:rounded-none",
'group-data-[last=true]:first:before:rounded-none',
'group-data-[last=true]:last:before:rounded-none',
],
}),
[]
@@ -141,7 +128,7 @@ export default function RunsTable({
{(column) => (
<TableColumn
key={column.uid}
align={column.uid === "actions" ? "center" : "start"}
align={column.uid === 'actions' ? 'center' : 'start'}
allowsSorting={column.sortable}
>
{column.name}
@@ -150,11 +137,7 @@ export default function RunsTable({
</TableHeader>
<TableBody emptyContent={messages.noRunsFound} items={sortedItems}>
{(item) => (
<TableRow key={item.id}>
{(columnKey) => (
<TableCell>{renderCell(item, columnKey)}</TableCell>
)}
</TableRow>
<TableRow key={item.id}>{(columnKey) => <TableCell>{renderCell(item, columnKey)}</TableCell>}</TableRow>
)}
</TableBody>
</Table>

View File

@@ -1,7 +1,7 @@
"use client";
import React from "react";
import { useState, useEffect } from "react";
import { useRouter } from "@/src/navigation";
'use client';
import React from 'react';
import { useState, useEffect } from 'react';
import { useRouter } from '@/src/navigation';
import {
Button,
Input,
@@ -17,28 +17,14 @@ import {
Dropdown,
DropdownMenu,
DropdownItem,
} from "@nextui-org/react";
import {
Save,
ArrowLeft,
Folder,
ChevronDown,
CopyPlus,
CopyMinus,
RotateCw,
} from "lucide-react";
import RunProgressChart from "./RunPregressDonutChart";
import TestCaseSelector from "./TestCaseSelector";
import { testRunStatus } from "@/config/selection";
import {
RunType,
RunCaseType,
RunCaseInfoType,
RunStatusCountType,
RunMessages,
} from "@/types/run";
import { CaseType } from "@/types/case";
import { FolderType } from "@/types/folder";
} from '@nextui-org/react';
import { Save, ArrowLeft, Folder, ChevronDown, CopyPlus, CopyMinus, RotateCw } from 'lucide-react';
import RunProgressChart from './RunPregressDonutChart';
import TestCaseSelector from './TestCaseSelector';
import { testRunStatus } from '@/config/selection';
import { RunType, RunCaseType, RunCaseInfoType, RunStatusCountType, RunMessages } from '@/types/run';
import { CaseType } from '@/types/case';
import { FolderType } from '@/types/folder';
import {
fetchRun,
updateRun,
@@ -48,16 +34,16 @@ import {
bulkCreateRunCases,
deleteRunCase,
bulkDeleteRunCases,
} from "../runsControl";
import { fetchFolders } from "../../folders/foldersControl";
import { fetchCases } from "../../folders/[folderId]/cases/caseControl";
import { useTheme } from "next-themes";
} from '../runsControl';
import { fetchFolders } from '../../folders/foldersControl';
import { fetchCases } from '../../folders/[folderId]/cases/caseControl';
import { useTheme } from 'next-themes';
const defaultTestRun = {
id: 0,
name: "",
name: '',
configurations: 0,
description: "",
description: '',
state: 0,
projectId: 0,
};
@@ -69,18 +55,12 @@ type Props = {
locale: string;
};
export default function RunEditor({
projectId,
runId,
messages,
locale,
}: Props) {
export default function RunEditor({ projectId, runId, messages, locale }: Props) {
const { theme, setTheme } = useTheme();
const [testRun, setTestRun] = useState<RunType>(defaultTestRun);
const [folders, setFolders] = useState([]);
const [runCases, setRunCases] = useState<RunCaseType[]>([]);
const [runStatusCounts, setRunStatusCounts] =
useState<RunStatusCountType[]>();
const [runStatusCounts, setRunStatusCounts] = useState<RunStatusCountType[]>();
const [selectedKeys, setSelectedKeys] = useState<Selection>(new Set([]));
const [selectedFolder, setSelectedFolder] = useState<FolderType>({});
const [testcases, setTestCases] = useState<CaseType[]>([]);
@@ -102,7 +82,7 @@ export default function RunEditor({
setFolders(foldersData);
setSelectedFolder(foldersData[0]);
} catch (error: any) {
console.error("Error in effect:", error.message);
console.error('Error in effect:', error.message);
}
}
@@ -120,9 +100,7 @@ export default function RunEditor({
// Check if each testCase has an association with testRun
// and add "isIncluded" property
const updatedTestCasesData = testCasesData.map((testCase) => {
const runCase = latestRunCases.find(
(runCase) => runCase.caseId === testCase.id
);
const runCase = latestRunCases.find((runCase) => runCase.caseId === testCase.id);
const isIncluded = runCase ? true : false;
const runStatus = runCase ? runCase.status : 0;
@@ -135,7 +113,7 @@ export default function RunEditor({
setTestCases(updatedTestCasesData);
} catch (error: any) {
console.error("Error fetching cases data:", error.message);
console.error('Error fetching cases data:', error.message);
}
}
}
@@ -155,10 +133,7 @@ export default function RunEditor({
});
};
const handleIncludeExcludeCase = async (
isInclude: boolean,
clickedTestCaseId: number
) => {
const handleIncludeExcludeCase = async (isInclude: boolean, clickedTestCaseId: number) => {
if (isInclude) {
const createdRunCase = await createRunCase(runId, clickedTestCaseId);
setRunCases((prevRunCases) => {
@@ -167,9 +142,7 @@ export default function RunEditor({
} else {
await deleteRunCase(runId, clickedTestCaseId);
setRunCases((prevRunCases) => {
return prevRunCases.filter(
(runCase) => runCase.caseId !== clickedTestCaseId
);
return prevRunCases.filter((runCase) => runCase.caseId !== clickedTestCaseId);
});
}
@@ -185,7 +158,7 @@ export default function RunEditor({
const handleBulkIncludeExcludeCases = async (isInclude: boolean) => {
let keys: number[] = [];
if (selectedKeys === "all") {
if (selectedKeys === 'all') {
keys = testcases.map((item) => item.id);
} else {
keys = Array.from(selectedKeys).map(Number);
@@ -218,7 +191,7 @@ export default function RunEditor({
setSelectedKeys(new Set([]));
};
const baseClass = "";
const baseClass = '';
const selectedClass = `${baseClass} bg-neutral-200 dark:bg-neutral-700`;
return (
@@ -230,9 +203,7 @@ export default function RunEditor({
isIconOnly
size="sm"
className="rounded-full bg-neutral-50 dark:bg-neutral-600"
onPress={() =>
router.push(`/projects/${projectId}/runs`, { locale: locale })
}
onPress={() => router.push(`/projects/${projectId}/runs`, { locale: locale })}
>
<ArrowLeft size={16} />
</Button>
@@ -272,11 +243,7 @@ export default function RunEditor({
</Tooltip>
</div>
<RunProgressChart
statusCounts={runStatusCounts}
messages={messages}
theme={theme}
/>
<RunProgressChart statusCounts={runStatusCounts} messages={messages} theme={theme} />
</div>
</div>
<div className="flex-grow">
@@ -287,7 +254,7 @@ export default function RunEditor({
label={messages.title}
value={testRun.name}
isInvalid={isNameInvalid}
errorMessage={isNameInvalid ? messages.pleaseEnter : ""}
errorMessage={isNameInvalid ? messages.pleaseEnter : ''}
onChange={(e) => {
setTestRun({ ...testRun, name: e.target.value });
}}
@@ -312,9 +279,7 @@ export default function RunEditor({
selectedKeys={[testRunStatus[testRun.state].uid]}
onSelectionChange={(e) => {
const selectedUid = e.anchorKey;
const index = testRunStatus.findIndex(
(template) => template.uid === selectedUid
);
const index = testRunStatus.findIndex((template) => template.uid === selectedUid);
setTestRun({ ...testRun, state: index });
}}
label={messages.status}
@@ -334,14 +299,10 @@ export default function RunEditor({
<div className="flex items-center justify-between">
<h6 className="h-8 font-bold">{messages.selectTestCase}</h6>
<div>
{(selectedKeys.size > 0 || selectedKeys === "all") && (
{(selectedKeys.size > 0 || selectedKeys === 'all') && (
<Dropdown>
<DropdownTrigger>
<Button
size="sm"
color="primary"
endContent={<ChevronDown size={16} />}
>
<Button size="sm" color="primary" endContent={<ChevronDown size={16} />}>
{messages.testCaseSelection}
</Button>
</DropdownTrigger>
@@ -371,14 +332,8 @@ export default function RunEditor({
<ListboxItem
key={index}
onClick={() => setSelectedFolder(folder)}
startContent={
<Folder size={20} color="#F7C24E" fill="#F7C24E" />
}
className={
selectedFolder && folder.id === selectedFolder.id
? selectedClass
: baseClass
}
startContent={<Folder size={20} color="#F7C24E" fill="#F7C24E" />}
className={selectedFolder && folder.id === selectedFolder.id ? selectedClass : baseClass}
>
{folder.name}
</ListboxItem>
@@ -391,12 +346,8 @@ export default function RunEditor({
selectedKeys={selectedKeys}
onSelectionChange={setSelectedKeys}
onStatusChange={handleChangeStatus}
onIncludeCase={(includeTestId) =>
handleIncludeExcludeCase(true, includeTestId)
}
onExcludeCase={(excludeCaseId) =>
handleIncludeExcludeCase(false, excludeCaseId)
}
onIncludeCase={(includeTestId) => handleIncludeExcludeCase(true, includeTestId)}
onExcludeCase={(excludeCaseId) => handleIncludeExcludeCase(false, excludeCaseId)}
messages={messages}
/>
</div>

View File

@@ -1,9 +1,9 @@
import React from "react";
import { useState, useEffect } from "react";
import dynamic from "next/dynamic";
import { testRunCaseStatus } from "@/config/selection";
import { RunStatusCountType, RunMessages } from "@/types/run";
const Chart = dynamic(() => import("react-apexcharts"), { ssr: false });
import React from 'react';
import { useState, useEffect } from 'react';
import dynamic from 'next/dynamic';
import { testRunCaseStatus } from '@/config/selection';
import { RunStatusCountType, RunMessages } from '@/types/run';
const Chart = dynamic(() => import('react-apexcharts'), { ssr: false });
type Props = {
statusCounts: RunStatusCountType[];
@@ -11,11 +11,7 @@ type Props = {
theme: string;
};
export default function RunProgressDounut({
statusCounts,
messages,
theme,
}: Props) {
export default function RunProgressDounut({ statusCounts, messages, theme }: Props) {
const [chartData, setChartData] = useState({
series: [],
options: {
@@ -37,10 +33,10 @@ export default function RunProgressDounut({
const legend = {
labels: {
colors: testRunCaseStatus.map((entry) => {
if (theme === "light") {
return "black";
if (theme === 'light') {
return 'black';
} else {
return "white";
return 'white';
}
}),
},
@@ -56,13 +52,5 @@ export default function RunProgressDounut({
updateChartDate();
}, [statusCounts, theme]);
return (
<Chart
options={chartData.options}
series={chartData.series}
type="donut"
width={"100%"}
height={"100%"}
/>
);
return <Chart options={chartData.options} series={chartData.series} type="donut" width={'100%'} height={'100%'} />;
}

View File

@@ -1,4 +1,4 @@
import { useState, useMemo } from "react";
import { useState, useMemo } from 'react';
import {
Table,
TableHeader,
@@ -13,7 +13,7 @@ import {
DropdownItem,
Selection,
SortDescriptor,
} from "@nextui-org/react";
} from '@nextui-org/react';
import {
ChevronDown,
MoreVertical,
@@ -24,10 +24,10 @@ import {
CircleDashed,
CircleX,
CircleSlash2,
} from "lucide-react";
import { priorities, testRunCaseStatus } from "@/config/selection";
import { CaseType } from "@/types/case";
import { RunsMessages } from "@/types/run";
} from 'lucide-react';
import { priorities, testRunCaseStatus } from '@/config/selection';
import { CaseType } from '@/types/case';
import { RunsMessages } from '@/types/run';
type Props = {
cases: CaseType[];
@@ -49,16 +49,16 @@ export default function TestCaseSelector({
messages,
}: Props) {
const headerColumns = [
{ name: messages.id, uid: "id", sortable: true },
{ name: messages.title, uid: "title", sortable: true },
{ name: messages.priority, uid: "priority", sortable: true },
{ name: messages.status, uid: "runStatus", sortable: true },
{ name: messages.actions, uid: "actions" },
{ name: messages.id, uid: 'id', sortable: true },
{ name: messages.title, uid: 'title', sortable: true },
{ name: messages.priority, uid: 'priority', sortable: true },
{ name: messages.status, uid: 'runStatus', sortable: true },
{ name: messages.actions, uid: 'actions' },
];
const [sortDescriptor, setSortDescriptor] = useState<SortDescriptor>({
column: "id",
direction: "ascending",
column: 'id',
direction: 'ascending',
});
const sortedItems = useMemo(() => {
@@ -67,23 +67,23 @@ export default function TestCaseSelector({
const second = b[sortDescriptor.column as keyof CaseType] as number;
const cmp = first < second ? -1 : first > second ? 1 : 0;
return sortDescriptor.direction === "descending" ? -cmp : cmp;
return sortDescriptor.direction === 'descending' ? -cmp : cmp;
});
}, [sortDescriptor, cases]);
const notIncludedCaseClass = "text-neutral-200 dark:text-neutral-600";
const chipBaseClass = "flex items-center text-default-600";
const notIncludedCaseClass = 'text-neutral-200 dark:text-neutral-600';
const chipBaseClass = 'flex items-center text-default-600';
const renderStatusIcon = (uid: string) => {
if (uid === "untested") {
if (uid === 'untested') {
return <Circle size={16} color="#d4d4d8" />;
} else if (uid === "passed") {
} else if (uid === 'passed') {
return <CircleCheck size={16} color="#17c964" />;
} else if (uid === "retest") {
} else if (uid === 'retest') {
return <CircleDashed size={16} color="#f5a524" />;
} else if (uid === "failed") {
} else if (uid === 'failed') {
return <CircleX size={16} color="#f31260" />;
} else if (uid === "skipped") {
} else if (uid === 'skipped') {
return <CircleSlash2 size={16} color="#52525b" />;
}
};
@@ -93,22 +93,18 @@ export default function TestCaseSelector({
const isIncluded = testCase.isIncluded;
switch (columnKey) {
case "priority":
case 'priority':
return (
<div
className={
isIncluded ? chipBaseClass : chipBaseClass + notIncludedCaseClass
}
>
<div className={isIncluded ? chipBaseClass : chipBaseClass + notIncludedCaseClass}>
<Circle
size={8}
color={isIncluded ? priorities[cellValue].color : "#d4d4d8"}
fill={isIncluded ? priorities[cellValue].color : "#d4d4d8"}
color={isIncluded ? priorities[cellValue].color : '#d4d4d8'}
fill={isIncluded ? priorities[cellValue].color : '#d4d4d8'}
/>
<div className="ms-3">{messages[priorities[cellValue].uid]}</div>
</div>
);
case "runStatus":
case 'runStatus':
return (
<Dropdown>
<DropdownTrigger>
@@ -116,15 +112,10 @@ export default function TestCaseSelector({
size="sm"
variant="light"
isDisabled={!isIncluded}
startContent={
isIncluded &&
renderStatusIcon(testRunCaseStatus[cellValue].uid)
}
startContent={isIncluded && renderStatusIcon(testRunCaseStatus[cellValue].uid)}
endContent={isIncluded && <ChevronDown size={16} />}
>
<span className="w-12">
{isIncluded && messages[testRunCaseStatus[cellValue].uid]}
</span>
<span className="w-12">{isIncluded && messages[testRunCaseStatus[cellValue].uid]}</span>
</Button>
</DropdownTrigger>
<DropdownMenu aria-label="test case actions">
@@ -140,7 +131,7 @@ export default function TestCaseSelector({
</DropdownMenu>
</Dropdown>
);
case "actions":
case 'actions':
return (
<Dropdown>
<DropdownTrigger>
@@ -173,18 +164,18 @@ export default function TestCaseSelector({
const classNames = useMemo(
() => ({
wrapper: ["min-w-3xl"],
th: ["bg-transparent", "text-default-500", "border-b", "border-divider"],
wrapper: ['min-w-3xl'],
th: ['bg-transparent', 'text-default-500', 'border-b', 'border-divider'],
td: [
// changing the rows border radius
// first
"group-data-[first=true]:first:before:rounded-none",
"group-data-[first=true]:last:before:rounded-none",
'group-data-[first=true]:first:before:rounded-none',
'group-data-[first=true]:last:before:rounded-none',
// middle
"group-data-[middle=true]:before:rounded-none",
'group-data-[middle=true]:before:rounded-none',
// last
"group-data-[last=true]:first:before:rounded-none",
"group-data-[last=true]:last:before:rounded-none",
'group-data-[last=true]:first:before:rounded-none',
'group-data-[last=true]:last:before:rounded-none',
],
}),
[]
@@ -211,7 +202,7 @@ export default function TestCaseSelector({
{(column) => (
<TableColumn
key={column.uid}
align={column.uid === "actions" ? "center" : "start"}
align={column.uid === 'actions' ? 'center' : 'start'}
allowsSorting={column.sortable}
>
{column.name}
@@ -220,13 +211,8 @@ export default function TestCaseSelector({
</TableHeader>
<TableBody emptyContent={messages.noCasesFound} items={sortedItems}>
{(item) => (
<TableRow
key={item.id}
className={!item.isIncluded ? notIncludedCaseClass : ""}
>
{(columnKey) => (
<TableCell>{renderCell(item, columnKey)}</TableCell>
)}
<TableRow key={item.id} className={!item.isIncluded ? notIncludedCaseClass : ''}>
{(columnKey) => <TableCell>{renderCell(item, columnKey)}</TableCell>}
</TableRow>
)}
</TableBody>

View File

@@ -1,53 +1,42 @@
import RunEditor from "./RunEditor";
import { useTranslations } from "next-intl";
import RunEditor from './RunEditor';
import { useTranslations } from 'next-intl';
export default function Page({
params,
}: {
params: { projectId: string; runId: string; locale: string };
}) {
const t = useTranslations("Run");
export default function Page({ params }: { params: { projectId: string; runId: string; locale: string } }) {
const t = useTranslations('Run');
const messages = {
backToRuns: t("back_to_runs"),
updating: t("updating"),
update: t("update"),
progress: t("progress"),
refresh: t("refresh"),
id: t("id"),
title: t("title"),
pleaseEnter: t("please_enter"),
description: t("description"),
new: t("new"),
inProgress: t("inProgress"),
underReview: t("underReview"),
rejected: t("rejected"),
done: t("done"),
closed: t("closed"),
priority: t("priority"),
actions: t("actions"),
status: t("status"),
critical: t("critical"),
high: t("high"),
medium: t("medium"),
low: t("low"),
untested: t("untested"),
passed: t("passed"),
failed: t("failed"),
retest: t("retest"),
skipped: t("skipped"),
selectTestCase: t("select_test_case"),
testCaseSelection: t("test_case_selection"),
includeInRun: t("include_in_run"),
excludeFromRun: t("exclude_from_run"),
noCasesFound: t("no_cases_found"),
backToRuns: t('back_to_runs'),
updating: t('updating'),
update: t('update'),
progress: t('progress'),
refresh: t('refresh'),
id: t('id'),
title: t('title'),
pleaseEnter: t('please_enter'),
description: t('description'),
new: t('new'),
inProgress: t('inProgress'),
underReview: t('underReview'),
rejected: t('rejected'),
done: t('done'),
closed: t('closed'),
priority: t('priority'),
actions: t('actions'),
status: t('status'),
critical: t('critical'),
high: t('high'),
medium: t('medium'),
low: t('low'),
untested: t('untested'),
passed: t('passed'),
failed: t('failed'),
retest: t('retest'),
skipped: t('skipped'),
selectTestCase: t('select_test_case'),
testCaseSelection: t('test_case_selection'),
includeInRun: t('include_in_run'),
excludeFromRun: t('exclude_from_run'),
noCasesFound: t('no_cases_found'),
};
return (
<RunEditor
projectId={params.projectId}
runId={params.runId}
messages={messages}
locale={params.locale}
/>
);
return <RunEditor projectId={params.projectId} runId={params.runId} messages={messages} locale={params.locale} />;
}

View File

@@ -1,31 +1,23 @@
import RunsPage from "./RunsPage";
import { useTranslations } from "next-intl";
import RunsPage from './RunsPage';
import { useTranslations } from 'next-intl';
export default function Page({
params,
}: {
params: { projectId: string; locale: string };
}) {
const t = useTranslations("Runs");
export default function Page({ params }: { params: { projectId: string; locale: string } }) {
const t = useTranslations('Runs');
const messages = {
runList: t("run_list"),
id: t("id"),
name: t("name"),
description: t("description"),
lastUpdate: t("last_update"),
actions: t("actions"),
newRun: t("new_run"),
deleteRun: t("delete_run"),
noRunsFound: t("no_runs_found"),
runList: t('run_list'),
id: t('id'),
name: t('name'),
description: t('description'),
lastUpdate: t('last_update'),
actions: t('actions'),
newRun: t('new_run'),
deleteRun: t('delete_run'),
noRunsFound: t('no_runs_found'),
};
return (
<>
<RunsPage
projectId={params.projectId}
locale={params.locale}
messages={messages}
/>
<RunsPage projectId={params.projectId} locale={params.locale} messages={messages} />
</>
);
}

View File

@@ -1,7 +1,3 @@
export default function ProjectsLayout({
children,
}: {
children: React.ReactNode;
}) {
export default function ProjectsLayout({ children }: { children: React.ReactNode }) {
return <>{children}</>;
}

View File

@@ -1,26 +1,26 @@
import ProjectsPage from "./ProjectsPage";
import { useTranslations } from "next-intl";
import ProjectsPage from './ProjectsPage';
import { useTranslations } from 'next-intl';
export default function Page(params: { locale: string }) {
const t = useTranslations("Projects");
const t = useTranslations('Projects');
const messages = {
projectList: t("projectList"),
project: t("project"),
newProject: t("new_project"),
editProject: t("edit_project"),
deleteProject: t("delete_project"),
id: t("id"),
name: t("name"),
detail: t("detail"),
lastUpdate: t("last_update"),
actions: t("actions"),
projectName: t("project_name"),
projectDetail: t("project_detail"),
close: t("close"),
create: t("create"),
update: t("update"),
pleaseEnter: t("please_enter"),
noProjectsFound: t("no_projects_found"),
projectList: t('projectList'),
project: t('project'),
newProject: t('new_project'),
editProject: t('edit_project'),
deleteProject: t('delete_project'),
id: t('id'),
name: t('name'),
detail: t('detail'),
lastUpdate: t('last_update'),
actions: t('actions'),
projectName: t('project_name'),
projectDetail: t('project_detail'),
close: t('close'),
create: t('create'),
update: t('update'),
pleaseEnter: t('please_enter'),
noProjectsFound: t('no_projects_found'),
};
return (
<>