Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/add assignee autocomplete #123

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions api/core/_transformers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,29 @@ import {
Repository,
RepositoryDto,
} from "@/types/repository";
import { User, UserDto } from "@/types/user";

export function dtoToTask(dto: TaskDto): Task {
return {
id: dto.id,
taskId: dto.issue_id,
isCertified: dto.certified,
labels: dto.labels ?? [],
repository: dtoToRepository(dto.repository),
project: dtoToProject(dto.repository.project),
user: dto.user ? dtoToUser(dto.user) : null,
repository: dto.repository ? dtoToRepository(dto.repository) : null,
project: dto.repository ? dtoToProject(dto.repository.project) : null,
title: dto.title,
description: dto.description,
createdAt: dto.issue_created_at,
url: dto.repository.url + `/issues/${dto.issue_id}`,
createdAt: dto.issue_created_at ? dto.issue_created_at : dto.created_at,
url: dto.repository ? dto.repository.url + `/issues/${dto.issue_id}` : null,
};
}

export function dtoToUser(dto: UserDto): User {
return {
id: dto.id,
username: dto.username,
avatar: dto.avatar,
};
}

Expand Down
2 changes: 1 addition & 1 deletion api/core/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { fetchFromApi } from "./_client";
import { dtoToTask, taskQueryParamsToDto } from "./_transformers";
import { getAllLanguages } from "./languages";

const TASKS_PATH = "/issues";
const TASKS_PATH = "/tasks";

export async function getTasks(
query: TaskQueryParams & PaginationQueryParams = DEFAULT_PAGINATION,
Expand Down
50 changes: 50 additions & 0 deletions app/api/update-task/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { NextRequest, NextResponse } from "next/server";

export async function POST(req: NextRequest) {
try {
const { taskId, assignedUserId } = await req.json();

if (!taskId || !assignedUserId) {
return NextResponse.json(
{ error: "Missing required fields" },
{ status: 400 },
);
}

const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
if (!GITHUB_TOKEN) {
return NextResponse.json(
{ error: "GitHub token not found" },
{ status: 500 },
);
}

const response = await fetch(`http://localhost:8000/tasks/${taskId}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${GITHUB_TOKEN}`,
},
body: JSON.stringify({ assignee_user_id: assignedUserId }),
});

const data = await response.json();

if (!response.ok) {
return NextResponse.json(
{ error: data?.message || "Failed to update task" },
{ status: response.status },
);
}

return NextResponse.json(
{ message: "Task updated successfully" },
{ status: 200 },
);
} catch (error) {
return NextResponse.json(
{ error: "Internal Server Error" },
{ status: 500 },
);
}
}
71 changes: 71 additions & 0 deletions components/table/assign-user.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"use client";

import { Autocomplete, AutocompleteItem } from "@nextui-org/autocomplete";
import { User } from "@/types/user";
import { useAsyncList } from "@react-stately/data";

export default function Users({ taskId }: { taskId: number }) {
const onSelectionChange = async (key: React.Key | null) => {
if (!key) return;

const selectedUserId = Number(key);
if (isNaN(selectedUserId)) {
console.error("Invalid user ID:", key);
return;
}

try {
const response = await fetch("/api/update-task", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
taskId,
assignedUserId: selectedUserId,
}),
});

if (!response.ok) {
throw new Error("Failed to assign user to task");
}

console.log("User assigned successfully");
} catch (error) {
console.error("Error assigning user:", error);
}
};

let list = useAsyncList<User>({
async load({ filterText }) {
let res = await fetch(
`http://localhost:8000/users?search=${filterText}`,
{
cache: "no-store",
},
);
let users = await res.json();
return { items: users };
},
});

return (
<Autocomplete
className="max-w-xs"
inputValue={list.filterText}
isLoading={list.isLoading}
items={list.items}
label="Select a user"
placeholder="Type to search..."
variant="bordered"
onSelectionChange={onSelectionChange}
onInputChange={list.setFilterText}
>
{(item: User) => (
<AutocompleteItem key={item.id} className="capitalize">
{item.username}
</AutocompleteItem>
)}
</Autocomplete>
);
}
24 changes: 23 additions & 1 deletion components/table/row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { formatDate } from "@/utils/date";
import { shuffleArray } from "@/utils/filters";
import { createUrl } from "@/utils/url";
import { CARNIVAL_NEW_LISTED_TASKS, CARNIVAL_WIP_TASKS } from "@/data/carnival";
import NextImage from "next/image";

const MAX_LABEL_WIDTH = 192;

Expand Down Expand Up @@ -67,7 +68,7 @@ interface IAvatarProps {
src: string | null;
}

const Avatar = ({ alt, src }: IAvatarProps) => {
export const Avatar = ({ alt, src }: IAvatarProps) => {
return (
<div className="bg-foreground rounded-md min-w-[40px] min-h-[40px] sm:min-w-[45px] sm:min-h-[45px] shrink-0 flex items-center justify-center">
{src !== null && (
Expand All @@ -86,6 +87,27 @@ const Avatar = ({ alt, src }: IAvatarProps) => {

Project.Avatar = Avatar;

interface IUserAvatarProps {
alt: string;
src: string | null;
}

export const UserAvatar = ({ alt, src }: IAvatarProps) => {
return (
<div className="relative border bg-foreground overflow-hidden rounded-full min-w-[45px] min-h-[45px] shrink-0 flex items-center justify-center">
{src !== null && (
<NextImage
className="pointer-events-none absolute -left-0 -top-0 h-[45px] w-[45px] max-w-[initial] transition-opacity z-10 opacity-100 object-cover object-center"
src={src}
alt={alt}
height={40}
width={40}
/>
)}
</div>
);
};

interface IContentProps {
id: number;
title: string;
Expand Down
22 changes: 21 additions & 1 deletion components/table/static-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,14 @@ import {
import { Task } from "@/types/task";

import TaskModal from "./task-modal";
import { ExternalLink, Content, Time, Project, ApplyButton } from "./row";
import {
ExternalLink,
Content,
Time,
Project,
ApplyButton,
UserAvatar,
} from "./row";
import { getIconSrc } from "@/utils/icons";

const DEFAULT_EMPTY = "No contributions to display yet";
Expand Down Expand Up @@ -64,6 +71,7 @@ const StaticTable = ({
{ name: "PROJECT", uid: "project" },
{ name: "CONTENT", uid: "content" },
{ name: "LABELS", uid: "labels" },
{ name: "ASSIGNEE", uid: "assignee" },
{ name: "DATE", uid: "date" },
{ name: "ACTIONS", uid: "actions" },
]);
Expand Down Expand Up @@ -115,6 +123,17 @@ const StaticTable = ({
/>
);
}
case "assignee": {
const { user } = item;
return user ? (
<UserAvatar
alt={`${user?.username} avatar`}
src={user.avatar ?? null}
/>
) : (
""
);
}
case "date":
return (
<div className="flex flex-col items-center gap-2">
Expand Down Expand Up @@ -142,6 +161,7 @@ const StaticTable = ({
{ name: "PROJECT", uid: "project" },
{ name: "CONTENT", uid: "content" },
...(isLaptop ? [{ name: "LABELS", uid: "labels" }] : []),
{ name: "ASSIGNEE", uid: "assignee" },
{ name: "DATE", uid: "date" },
...(isMobile ? [] : [{ name: "ACTIONS", uid: "actions" }]),
]);
Expand Down
2 changes: 1 addition & 1 deletion next.config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/** @type {import('next').NextConfig} */
const cspHeader = `
default-src 'self';
connect-src 'self' https://api.morekudos.com/ https://www.google-analytics.com/;
connect-src 'self' http://localhost:8000 https://api.morekudos.com/ https://www.google-analytics.com/ http://localhost:8000/users;
script-src 'self' 'unsafe-eval' 'unsafe-inline' https://va.vercel-scripts.com/ https://www.google-analytics.com/ https://tagmanager.google.com/ https://www.googletagmanager.com/;
style-src 'self' 'unsafe-inline';
img-src 'self' https://cryptologos.cc/ https://avatars.githubusercontent.com/ blob: data:;
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
},
"dependencies": {
"@next/third-parties": "15.1.3",
"@nextui-org/autocomplete": "^2.3.9",
"@nextui-org/button": "2.2.8",
"@nextui-org/card": "^2.2.8",
"@nextui-org/checkbox": "^2.3.7",
Expand All @@ -32,6 +33,8 @@
"@nextui-org/table": "^2.2.7",
"@nextui-org/theme": "2.4.4",
"@nextui-org/tooltip": "^2.2.6",
"@nextui-org/use-infinite-scroll": "^2.2.2",
"@react-stately/data": "^3.12.1",
"@tanstack/react-query": "^5.62.11",
"@tanstack/react-query-devtools": "^5.62.11",
"@types/react": "19.0.2",
Expand Down
Loading
Loading