fix(filters): expand ordering select on mobile
This commit is contained in:
@@ -9,7 +9,7 @@ import { Plus, Archive, Trash2, Pencil } from "lucide-react";
|
||||
|
||||
import FilterBar from "../components/FilterBar";
|
||||
import { Button } from "../components/ui/button";
|
||||
import { Card, CardHeader, CardTitle, CardContent } from "../components/ui/card";
|
||||
import { Card } from "../components/ui/card";
|
||||
import { Modal } from "../components/Modal";
|
||||
import { toast } from "sonner";
|
||||
import { Input } from "../components/ui/input";
|
||||
@@ -21,8 +21,8 @@ import {
|
||||
canWorkspace,
|
||||
} from "../lib/permissions";
|
||||
|
||||
export const Projects: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
export const Projects: React.FC = () => {
|
||||
const { t, lang } = useTranslation();
|
||||
const { activeWorkspace } = useWorkspace();
|
||||
const workspaceRole = activeWorkspace?.my_role;
|
||||
const canCreateProject = canWorkspace(workspaceRole, PROJECTS_CREATE);
|
||||
@@ -100,7 +100,7 @@ export const Projects: React.FC = () => {
|
||||
setProjectToDelete(project);
|
||||
};
|
||||
|
||||
const confirmDelete = async () => {
|
||||
const confirmDelete = async () => {
|
||||
if (!deleteModal.project) return;
|
||||
try {
|
||||
const deletedId = deleteModal.project.id;
|
||||
@@ -118,7 +118,20 @@ export const Projects: React.FC = () => {
|
||||
} catch (error) {
|
||||
toast.error(t.projects?.deleteError || 'Failed to delete project');
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const formatDate = (dateStr: string | undefined) => {
|
||||
if (!dateStr) return "-"
|
||||
try {
|
||||
const date = new Date(dateStr)
|
||||
return new Intl.DateTimeFormat(lang === "fa" ? "fa-IR" : "en-US", {
|
||||
dateStyle: "long",
|
||||
timeZone: "Asia/Tehran",
|
||||
}).format(date)
|
||||
} catch {
|
||||
return dateStr
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
@@ -161,74 +174,76 @@ export const Projects: React.FC = () => {
|
||||
searchPlaceholder={t.projects?.searchPlaceholder || 'Search projects...'}
|
||||
/>
|
||||
|
||||
{loading ? (
|
||||
<div className="p-12 flex justify-center text-slate-500">
|
||||
<div className="animate-pulse">{t.projects?.loading || 'Loading...'}</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col flex-1">
|
||||
<div className="flex flex-col gap-4 mb-6">
|
||||
{projects.map((project) => (
|
||||
<Card key={project.id} className="flex flex-col text-slate-800 dark:text-slate-100 dark:bg-slate-800 dark:border-slate-700 shadow-sm">
|
||||
<CardContent className="flex flex-col sm:flex-row items-start sm:items-center justify-between py-4 px-6 gap-4">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<CardTitle className="text-lg line-clamp-1">
|
||||
{project.name}
|
||||
</CardTitle>
|
||||
</div>
|
||||
<p className="text-sm text-slate-500 dark:text-slate-400 line-clamp-1">
|
||||
{project.client ? `${t.projects?.client || "Client"}: ${project.client.name}` : t.projects?.noClient || 'No client'}
|
||||
</p>
|
||||
|
||||
{project.description && (
|
||||
<p className="text-sm text-slate-600 dark:text-slate-300 mt-2 line-clamp-2">
|
||||
{project.description}
|
||||
</p>
|
||||
)}
|
||||
|
||||
</div>
|
||||
|
||||
{(canEditProject || canDeleteProject) && (
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
{canEditProject && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setEditingProject(project)}
|
||||
className="h-8 w-8 text-slate-400 hover:text-blue-600 hover:bg-blue-50 dark:hover:text-blue-400 dark:hover:bg-blue-900/20"
|
||||
title={t.actions?.edit || 'Edit'}
|
||||
>
|
||||
<Pencil className="w-4 h-4" />
|
||||
</Button>
|
||||
)}
|
||||
{loading ? (
|
||||
<div className="p-12 flex justify-center text-slate-500">
|
||||
<div className="animate-pulse">{t.projects?.loading || 'Loading...'}</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col flex-1">
|
||||
<Card className="overflow-hidden dark:bg-slate-800 dark:border-slate-700 mb-6">
|
||||
<div className="p-0">
|
||||
{projects.length === 0 ? (
|
||||
<div className="py-16 flex flex-col items-center justify-center">
|
||||
<p className="text-slate-500 dark:text-slate-400 font-medium">{t.projects?.emptyState || 'No projects found'}</p>
|
||||
</div>
|
||||
) : (
|
||||
<ul className="divide-y divide-slate-200 dark:divide-slate-800">
|
||||
{projects.map((project) => (
|
||||
<li
|
||||
key={project.id}
|
||||
className="p-4 hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors flex items-center justify-between gap-4"
|
||||
>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h4 className="font-medium text-slate-900 dark:text-white truncate">{project.name}</h4>
|
||||
<p className="text-sm text-slate-500 dark:text-slate-400 mt-1 truncate">
|
||||
{project.client ? `${t.projects?.client || "Client"}: ${project.client.name}` : t.projects?.noClient || "No client"}
|
||||
</p>
|
||||
{project.description && (
|
||||
<p className="text-sm text-slate-500 dark:text-slate-400 mt-1 truncate">
|
||||
{project.description}
|
||||
</p>
|
||||
)}
|
||||
<div className="text-[11px] text-slate-400 mt-3 font-medium">
|
||||
{(t.projects as any)?.addedOn || "Added on"}: {formatDate(project.created_at)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{canDeleteProject && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setDeleteModal({ isOpen: true, project })}
|
||||
className="h-8 w-8 text-slate-400 hover:text-red-600 hover:bg-red-50 dark:hover:text-red-400 dark:hover:bg-red-900/20"
|
||||
title={t.actions?.delete || 'Delete'}
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</Button>
|
||||
{(canEditProject || canDeleteProject) && (
|
||||
<div className="flex items-center gap-1 shrink-0">
|
||||
{canEditProject && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setEditingProject(project)}
|
||||
className="h-8 w-8 text-slate-400 hover:text-blue-600 hover:bg-blue-50 dark:hover:text-blue-400 dark:hover:bg-blue-900/20"
|
||||
title={t.actions?.edit || "Edit"}
|
||||
>
|
||||
<Pencil className="w-4 h-4" />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{canDeleteProject && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setDeleteModal({ isOpen: true, project })}
|
||||
className="h-8 w-8 text-slate-400 hover:text-red-600 hover:bg-red-50 dark:hover:text-red-400 dark:hover:bg-red-900/20"
|
||||
title={t.actions?.delete || "Delete"}
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
|
||||
{projects.length === 0 && (
|
||||
<div className="py-16 flex flex-col items-center justify-center border-2 border-dashed border-slate-200 dark:border-slate-800 rounded-2xl">
|
||||
<p className="text-slate-500 dark:text-slate-400 font-medium">{t.projects?.emptyState || 'No projects found'}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
totalCount={totalItems}
|
||||
limit={limit}
|
||||
onPageChange={setCurrentPage}
|
||||
|
||||
Reference in New Issue
Block a user