Spaces:
Sleeping
Sleeping
feat: class list in project viewer (#29)
Browse files<img width="1904" alt="image"
src="https://github.com/landing-ai/vision-agent-ui/assets/5669963/ed78e0f0-1bed-4480-aa0d-1d40a3b4d9b2">
- app/project/[projectId]/page.tsx +7 -2
- components/project/ClassBar.tsx +22 -0
- components/project/MediaGrid.tsx +2 -2
- components/ui/Chip.tsx +8 -5
- lib/fetch/index.ts +20 -0
app/project/[projectId]/page.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
import MediaGrid from '@/components/project/MediaGrid';
|
| 2 |
-
import { fetchProjectMedia } from '@/lib/fetch';
|
| 3 |
import ProjectChat from '@/components/project/ProjectChat';
|
|
|
|
| 4 |
|
| 5 |
interface PageProps {
|
| 6 |
params: {
|
|
@@ -11,12 +12,16 @@ interface PageProps {
|
|
| 11 |
export default async function Page({ params }: PageProps) {
|
| 12 |
const { projectId } = params;
|
| 13 |
|
| 14 |
-
const mediaList = await
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
return (
|
| 17 |
<div className="pt-4 md:pt-10 h-full">
|
| 18 |
<div className="flex h-full">
|
| 19 |
<div className="w-1/2 relative border-r border-gray-300 overflow-auto">
|
|
|
|
| 20 |
<MediaGrid mediaList={mediaList} />
|
| 21 |
</div>
|
| 22 |
<div className="w-1/2 relative overflow-auto">
|
|
|
|
| 1 |
import MediaGrid from '@/components/project/MediaGrid';
|
| 2 |
+
import { fetchProjectClass, fetchProjectMedia } from '@/lib/fetch';
|
| 3 |
import ProjectChat from '@/components/project/ProjectChat';
|
| 4 |
+
import ClassBar from '@/components/project/ClassBar';
|
| 5 |
|
| 6 |
interface PageProps {
|
| 7 |
params: {
|
|
|
|
| 12 |
export default async function Page({ params }: PageProps) {
|
| 13 |
const { projectId } = params;
|
| 14 |
|
| 15 |
+
const [mediaList, classList] = await Promise.all([
|
| 16 |
+
fetchProjectMedia({ projectId: Number(projectId) }),
|
| 17 |
+
fetchProjectClass({ projectId: Number(projectId) }),
|
| 18 |
+
]);
|
| 19 |
|
| 20 |
return (
|
| 21 |
<div className="pt-4 md:pt-10 h-full">
|
| 22 |
<div className="flex h-full">
|
| 23 |
<div className="w-1/2 relative border-r border-gray-300 overflow-auto">
|
| 24 |
+
<ClassBar classList={classList} />
|
| 25 |
<MediaGrid mediaList={mediaList} />
|
| 26 |
</div>
|
| 27 |
<div className="w-1/2 relative overflow-auto">
|
components/project/ClassBar.tsx
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { ClassDetails } from '@/lib/fetch';
|
| 2 |
+
import Chip from '../ui/Chip';
|
| 3 |
+
|
| 4 |
+
export interface ClassBarProps {
|
| 5 |
+
classList: ClassDetails[];
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
export default async function ClassBar({ classList }: ClassBarProps) {
|
| 9 |
+
return (
|
| 10 |
+
<div className="border-b border-gray-300 px-3 pb-3 max-w-3xl mx-auto">
|
| 11 |
+
{classList.map(classItem => {
|
| 12 |
+
return (
|
| 13 |
+
<Chip
|
| 14 |
+
key={classItem.id}
|
| 15 |
+
value={classItem.name}
|
| 16 |
+
className="px-3 py-1 my-1"
|
| 17 |
+
/>
|
| 18 |
+
);
|
| 19 |
+
})}
|
| 20 |
+
</div>
|
| 21 |
+
);
|
| 22 |
+
}
|
components/project/MediaGrid.tsx
CHANGED
|
@@ -7,8 +7,8 @@ export default function MediaGrid({
|
|
| 7 |
mediaList: MediaDetails[];
|
| 8 |
}) {
|
| 9 |
return (
|
| 10 |
-
<div className="relative size-full p-
|
| 11 |
-
<div className="columns-1 sm:columns-1 md:columns-2 lg:columns-2 xl:columns:3 gap-
|
| 12 |
{mediaList.map(media => (
|
| 13 |
<MediaTile key={media.id} media={media} />
|
| 14 |
))}
|
|
|
|
| 7 |
mediaList: MediaDetails[];
|
| 8 |
}) {
|
| 9 |
return (
|
| 10 |
+
<div className="relative size-full p-3 max-w-3xl mx-auto">
|
| 11 |
+
<div className="columns-1 sm:columns-1 md:columns-2 lg:columns-2 xl:columns:3 gap-3 [&>img:not(:first-child)]:mt-3">
|
| 12 |
{mediaList.map(media => (
|
| 13 |
<MediaTile key={media.id} media={media} />
|
| 14 |
))}
|
components/ui/Chip.tsx
CHANGED
|
@@ -7,14 +7,17 @@ export interface ChipProps {
|
|
| 7 |
className?: string;
|
| 8 |
}
|
| 9 |
|
| 10 |
-
const Chip: React.FC<ChipProps> = ({
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
return (
|
| 12 |
<div
|
| 13 |
className={cn(
|
| 14 |
-
|
| 15 |
-
color
|
| 16 |
-
color === 'yellow' && 'bg-yellow-100 text-yellow-500',
|
| 17 |
-
color === 'purple' && 'bg-purple-100 text-purple-500',
|
| 18 |
className,
|
| 19 |
)}
|
| 20 |
>
|
|
|
|
| 7 |
className?: string;
|
| 8 |
}
|
| 9 |
|
| 10 |
+
const Chip: React.FC<ChipProps> = ({
|
| 11 |
+
label,
|
| 12 |
+
value,
|
| 13 |
+
className,
|
| 14 |
+
color = 'gray',
|
| 15 |
+
}) => {
|
| 16 |
return (
|
| 17 |
<div
|
| 18 |
className={cn(
|
| 19 |
+
'inline-flex items-center px-1.5 rounded-full text-xs mr-2',
|
| 20 |
+
`bg-${color}-100 text-${color}-500`,
|
|
|
|
|
|
|
| 21 |
className,
|
| 22 |
)}
|
| 23 |
>
|
lib/fetch/index.ts
CHANGED
|
@@ -121,3 +121,23 @@ export const fetchProjectMedia = clefApiBuilder<
|
|
| 121 |
{ projectId: number },
|
| 122 |
MediaDetails[]
|
| 123 |
>('api/admin/vision-agent/project/media');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
{ projectId: number },
|
| 122 |
MediaDetails[]
|
| 123 |
>('api/admin/vision-agent/project/media');
|
| 124 |
+
|
| 125 |
+
export type ClassDetails = {
|
| 126 |
+
id: number;
|
| 127 |
+
name: string;
|
| 128 |
+
descriptionText?: string | null;
|
| 129 |
+
orgId: number;
|
| 130 |
+
projectId: number;
|
| 131 |
+
createdAt: string;
|
| 132 |
+
updatedAt: string | null;
|
| 133 |
+
color?: string | null;
|
| 134 |
+
};
|
| 135 |
+
|
| 136 |
+
/**
|
| 137 |
+
* Fetch all active classes of a given project
|
| 138 |
+
* @author https://github.com/landing-ai/landing-platform/blob/mingrui-04-08-meaningful-project/packages/server-clef/src/main_app/controllers/admin/get_admin_meaningful_project_controller.ts
|
| 139 |
+
*/
|
| 140 |
+
export const fetchProjectClass = clefApiBuilder<
|
| 141 |
+
{ projectId: number },
|
| 142 |
+
ClassDetails[]
|
| 143 |
+
>('api/admin/vision-agent/project/class');
|