Skip to content

Commit 9fd8831

Browse files
committed
Change applicant mobile view
1 parent 67e27d6 commit 9fd8831

3 files changed

Lines changed: 170 additions & 214 deletions

File tree

‎apps/codebility/app/home/applicants/_components/_table/applicantColumns.tsx‎

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,21 @@ export const applicantsColumns: ColumnDef<NewApplicantType>[] = [
2828
}
2929
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
3030
aria-label="Select all"
31-
className=""
3231
/>
3332
),
3433
cell: ({ row }) => (
3534
<Checkbox
3635
checked={row.getIsSelected()}
3736
onCheckedChange={(value) => row.toggleSelected(!!value)}
3837
aria-label="Select row"
39-
className="mx-0 mt-8 px-0 xl:mt-2"
38+
className="mx-0 mt-8 px-0 xl:mt-0"
4039
/>
4140
),
4241
enableSorting: false,
4342
enableHiding: false,
43+
meta: {
44+
className: "align-top xl:align-middle",
45+
},
4446
},
4547
{
4648
id: "applicant",
@@ -350,46 +352,46 @@ export const applicantsColumns: ColumnDef<NewApplicantType>[] = [
350352
className: "m-0 px-0",
351353
},
352354
enableHiding: true,
353-
},
355+
},
354356

355-
{
357+
{
356358
id: "reminded",
357359
header: ({ column }) => {
358360
return (
359-
<Button
360-
variant="ghost"
361-
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
362-
className="flex items-center justify-center gap-1 text-xs font-medium text-gray-700 dark:text-gray-200"
363-
>
364-
Reminded
365-
<ArrowUpDown className="ml-2 h-4 w-4" />
366-
</Button>
361+
<Button
362+
variant="ghost"
363+
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
364+
className="flex items-center justify-center gap-1 text-xs font-medium text-gray-700 dark:text-gray-200"
365+
>
366+
Reminded
367+
<ArrowUpDown className="ml-2 h-4 w-4" />
368+
</Button>
367369
);
368370
},
369371
cell: ({ row }) => {
370372
const applicant: NewApplicantType = row.original;
371373

372374
return (
373-
<div className="flex items-center justify-center px-2 py-1">
374-
<div className="flex flex-col items-center gap-0.5">
375-
<span className="inline-flex h-5 items-center justify-center rounded-full bg-gray-100 px-1 text-xs font-medium text-gray-800 dark:bg-gray-800 dark:text-gray-100">
376-
{applicant.applicant?.reminded_count ?? 0}
377-
</span>
378-
<span className="text-[10px] text-gray-500 dark:text-gray-400">
379-
{applicant.applicant?.last_reminded_date &&
380-
new Date(
381-
applicant.applicant.last_reminded_date,
382-
).toLocaleDateString()}
383-
</span>
375+
<div className="flex items-center justify-center px-2 py-1">
376+
<div className="flex flex-col items-center gap-0.5">
377+
<span className="inline-flex h-5 items-center justify-center rounded-full bg-gray-100 px-1 text-xs font-medium text-gray-800 dark:bg-gray-800 dark:text-gray-100">
378+
{applicant.applicant?.reminded_count ?? 0}
379+
</span>
380+
<span className="text-[10px] text-gray-500 dark:text-gray-400">
381+
{applicant.applicant?.last_reminded_date &&
382+
new Date(
383+
applicant.applicant.last_reminded_date,
384+
).toLocaleDateString()}
385+
</span>
386+
</div>
384387
</div>
385-
</div>
386388
);
387389
},
388390
meta: {
389391
className: "m-0 px-0",
390392
},
391-
},
392-
{
393+
},
394+
{
393395
id: "actions",
394396
header: "Actions",
395397
cell: ({ row }) => {
Lines changed: 103 additions & 182 deletions
Original file line numberDiff line numberDiff line change
@@ -1,197 +1,118 @@
11
"use client";
22

3-
import React, { memo } from "react";
4-
import Image from "next/image";
5-
import Link from "next/link";
6-
import DefaultAvatar from "@/components/DefaultAvatar";
7-
import { IconGithub, IconLink } from "@/public/assets/svgs";
8-
import { cn } from "@/lib/utils";
3+
import React from "react";
4+
import { getTestDate } from "@/app/applicant/waiting/_service/util";
95
import {
10-
Table as TableInstance,
11-
} from "@tanstack/react-table";
6+
Table,
7+
TableBody,
8+
TableCell,
9+
TableHead,
10+
TableHeader,
11+
TableRow,
12+
} from "@/components/ui/table";
13+
import { cn } from "@/lib/utils";
14+
import { flexRender, Table as ReactTable } from "@tanstack/react-table";
1215

1316
import { NewApplicantType } from "../../_service/types";
14-
import ApplicantRowActionButton from "./applicantRowActionButton";
15-
import ApplicantTestTimeRemaining from "../applicantTestTimeRemaining";
16-
import ApplicantActionButton from "./applicantActionButton";
1717

18-
function ApplicantMobileTableComponent<TData extends NewApplicantType>({
18+
interface ApplicantMobileTableProps<TData extends NewApplicantType> {
19+
table: ReactTable<TData>;
20+
}
21+
22+
function ApplicantMobileTable<TData extends NewApplicantType>({
1923
table,
20-
}: {
21-
table: TableInstance<TData>;
22-
}) {
24+
}: ApplicantMobileTableProps<TData>) {
25+
const toBeFailed = (
26+
testTaken: string | null | undefined,
27+
forkUrl: string | null | undefined,
28+
): Boolean => {
29+
if (!testTaken) return false;
30+
if (forkUrl) return false;
31+
32+
const currentDate = new Date();
33+
const testTakenDate = getTestDate(new Date(testTaken || "") || new Date());
34+
const difference = testTakenDate.getTime() - currentDate.getTime();
35+
36+
return difference <= 0;
37+
};
38+
2339
return (
24-
<>
25-
{/* Mobile card layout for smaller screens */}
26-
<div className="block xl:hidden">
27-
{/* Sleek cards for each applicant */}
28-
<div className="space-y-1">
29-
{table.getRowModel().rows?.length ? (
30-
table.getRowModel().rows.map((row) => {
31-
const applicant = row.original;
32-
return (
33-
<div
34-
key={`${row.id}-${row.index}`}
40+
<Table className="table">
41+
{/* Table header */}
42+
<TableHeader>
43+
{table.getHeaderGroups().map((headerGroup) => (
44+
<TableRow key={headerGroup.id}>
45+
{headerGroup.headers
46+
.filter((_, index) => index <= 1) // 👈 show 1st and 2nd columns
47+
.map((header) => (
48+
<TableHead
49+
key={header.id}
3550
className={cn(
36-
"rounded-md border bg-slate-100 p-2 shadow-sm transition-all hover:shadow-md dark:bg-slate-800 dark:border-slate-600",
37-
applicant.application_status === "testing" &&
38-
applicant.applicant?.fork_url &&
39-
"bg-green-50 border-green-300 dark:bg-green-900/20 dark:border-green-700",
40-
applicant.application_status === "onboarding" &&
41-
"bg-customBlue-50 border-customBlue-300 dark:bg-customBlue-900/20 dark:border-customBlue-700",
42-
applicant.application_status === "denied" &&
43-
"bg-red-50 border-red-300 dark:bg-red-900/20 dark:border-red-700",
51+
"whitespace-nowrap",
52+
(header.column.columnDef.meta as any)?.className,
4453
)}
4554
>
46-
{/* Header with checkbox, avatar, name, and action */}
47-
<div className="mb-1.5 flex items-center justify-between">
48-
<div className="flex items-center gap-2">
49-
<div
50-
onClick={() => row.toggleSelected(!row.getIsSelected())}
51-
className={cn(
52-
"h-5 w-5 rounded border-2 cursor-pointer transition-all flex items-center justify-center",
53-
row.getIsSelected()
54-
? "bg-blue-500 border-blue-500"
55-
: "bg-white border-gray-300 dark:bg-gray-700 dark:border-gray-500"
56-
)}
57-
>
58-
{row.getIsSelected() && (
59-
<span className="text-white text-sm font-bold">✓</span>
60-
)}
61-
</div>
62-
<div className="h-7 w-7 overflow-hidden rounded-full bg-gray-200 dark:bg-gray-700">
63-
{applicant.image_url ? (
64-
<Image
65-
src={applicant.image_url}
66-
alt={`${applicant.first_name} avatar`}
67-
width={28}
68-
height={28}
69-
className="h-full w-full object-cover"
70-
/>
71-
) : (
72-
<DefaultAvatar size={28} />
73-
)}
74-
</div>
75-
<div className="min-w-0 flex-1">
76-
<h3 className="truncate text-xs font-semibold text-gray-900 dark:text-gray-100">
77-
{applicant.first_name.charAt(0).toUpperCase() + applicant.first_name.slice(1)} {" "}
78-
{applicant.last_name.charAt(0).toUpperCase() + applicant.last_name.slice(1)}
79-
</h3>
80-
<p className="truncate text-[11px] text-gray-600 dark:text-gray-400">
81-
{applicant.email_address}
82-
</p>
83-
</div>
84-
</div>
85-
<ApplicantActionButton applicant={applicant} />
86-
</div>
87-
88-
{/* Compact info grid */}
89-
<div className="grid grid-cols-2 gap-x-3 gap-y-1 text-[11px]">
90-
{/* Left column */}
91-
<div className="space-y-1">
92-
{/* Position */}
93-
<div className="flex items-center gap-1">
94-
<span className="text-[10px] opacity-60">💼</span>
95-
<span className="font-medium text-gray-700 dark:text-gray-300">
96-
{applicant.display_position || "Not specified"}
97-
</span>
98-
</div>
99-
{/* Experience */}
100-
<div className="flex items-center gap-1">
101-
<span className="text-[10px] opacity-60">⏱</span>
102-
<span className="text-gray-600 dark:text-gray-400">
103-
{applicant.years_of_experience !== undefined
104-
? `${applicant.years_of_experience} years`
105-
: "No exp"}
106-
</span>
107-
</div>
108-
</div>
55+
{header.isPlaceholder
56+
? null
57+
: flexRender(
58+
header.column.columnDef.header,
59+
header.getContext(),
60+
)}
61+
</TableHead>
62+
))}
63+
</TableRow>
64+
))}
65+
</TableHeader>
10966

110-
{/* Right column */}
111-
<div className="space-y-1 text-right">
112-
{/* Applied date */}
113-
<div className="text-gray-600 dark:text-gray-400">
114-
<span className="text-[10px] opacity-60">📅 </span>
115-
{applicant.date_applied
116-
? new Date(applicant.date_applied).toLocaleDateString('en-US', {
117-
month: 'short',
118-
day: 'numeric',
119-
year: '2-digit'
120-
})
121-
: "N/A"}
122-
</div>
123-
{/* Links and test status */}
124-
<div className="flex items-center justify-end gap-1.5">
125-
{applicant.application_status !== "testing" && applicant.github && (
126-
<Link
127-
href={applicant.github}
128-
target="_blank"
129-
className="text-gray-600 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-200"
130-
>
131-
<IconGithub className="h-3 w-3" />
132-
</Link>
133-
)}
134-
{applicant.application_status !== "testing" && applicant.portfolio_website && (
135-
<Link
136-
href={applicant.portfolio_website}
137-
target="_blank"
138-
className="text-gray-600 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-200"
139-
>
140-
<IconLink className="h-3 w-3" />
141-
</Link>
142-
)}
143-
{applicant.application_status === "testing" && (
144-
<>
145-
<ApplicantTestTimeRemaining applicant={applicant} />
146-
{applicant.applicant?.fork_url && (
147-
<Link
148-
href={applicant.applicant.fork_url}
149-
target="_blank"
150-
rel="noopener noreferrer"
151-
className="text-customBlue-600 hover:text-customBlue-800 dark:text-customBlue-400 dark:hover:text-customBlue-300"
152-
>
153-
<IconLink className="h-2.5 w-2.5" />
154-
</Link>
155-
)}
156-
</>
157-
)}
158-
</div>
159-
</div>
160-
</div>
161-
162-
{/* Tech stacks - only show if not in testing status */}
163-
{applicant.application_status !== "testing" && applicant.tech_stacks && applicant.tech_stacks.length > 0 && (
164-
<div className="mt-1 flex items-center gap-1 overflow-hidden">
165-
<span className="text-[10px] text-gray-500 dark:text-gray-400">💻</span>
166-
<div className="flex gap-0.5 overflow-x-auto">
167-
{applicant.tech_stacks.slice(0, 4).map((stack) => (
168-
<span
169-
key={stack}
170-
className="rounded bg-gray-100 px-1 py-0.5 text-[10px] text-gray-600 dark:bg-gray-800 dark:text-gray-400"
171-
>
172-
{stack}
173-
</span>
174-
))}
175-
{applicant.tech_stacks.length > 4 && (
176-
<span className="text-[10px] text-gray-500 dark:text-gray-400">
177-
+{applicant.tech_stacks.length - 4}
178-
</span>
179-
)}
180-
</div>
181-
</div>
182-
)}
183-
</div>
184-
);
185-
})
186-
) : (
187-
<div className="rounded-md border bg-white p-3 text-center dark:bg-gray-900 dark:border-gray-700">
188-
<p className="text-xs text-gray-500 dark:text-gray-400">No results found.</p>
189-
</div>
190-
)}
191-
</div>
192-
</div>
193-
</>
67+
<TableBody>
68+
{table.getRowModel().rows?.length ? (
69+
table.getRowModel().rows.map((row) => (
70+
<TableRow
71+
key={`${row.id}-${row.index}`}
72+
data-state={row.getIsSelected() && "selected"}
73+
className={cn(
74+
(row.original as NewApplicantType).application_status ===
75+
"testing" &&
76+
(row.original as NewApplicantType).applicant?.fork_url &&
77+
"bg-green bg-opacity-5",
78+
(row.original as NewApplicantType).application_status ===
79+
"testing" &&
80+
toBeFailed(
81+
(row.original as NewApplicantType).applicant?.test_taken,
82+
(row.original as NewApplicantType).applicant?.fork_url,
83+
) &&
84+
"bg-customRed-100 bg-opacity-5",
85+
)}
86+
>
87+
{row
88+
.getVisibleCells()
89+
.filter((_, index) => index <= 1) // 👈 show 1st and 2nd columns
90+
.map((cell) => (
91+
<TableCell
92+
key={cell.id}
93+
className={cn(
94+
(cell.column.columnDef.meta as any)?.className,
95+
)}
96+
>
97+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
98+
</TableCell>
99+
))}
100+
</TableRow>
101+
))
102+
) : (
103+
<TableRow>
104+
<TableCell
105+
// now we render 2 columns, so colspan = 2
106+
colSpan={2}
107+
className="h-24 text-center"
108+
>
109+
No results.
110+
</TableCell>
111+
</TableRow>
112+
)}
113+
</TableBody>
114+
</Table>
194115
);
195116
}
196117

197-
export default memo(ApplicantMobileTableComponent) as typeof ApplicantMobileTableComponent;
118+
export default ApplicantMobileTable;

0 commit comments

Comments
 (0)