fix: improve mobile responsiveness for tablo/project management pages
- Make project card grids responsive (single column on mobile, 2 cols on sm) - Remove fixed w-56 card width so cards fill available space on mobile - Convert modals to bottom-sheet style on mobile (items-end, rounded-t-2xl) - Add max-h-[90vh] + overflow-y-auto to modals for small screens - Increase touch targets to min 44px on action buttons and interactive elements - Refactor DashboardTaskList rows from rigid grid to flexible layout - Remove min-w-[600px] wrapper that forced horizontal scroll on task list - Make tab navigation horizontally scrollable instead of wrapping on mobile - Reduce left padding on etape child tasks for narrow screens - Stack modal action buttons vertically on mobile (flex-col-reverse) - Add responsive text sizing for headings and dates - Fix share dialog invite input to stack on mobile Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3daf720447
commit
6aea504667
10 changed files with 140 additions and 139 deletions
|
|
@ -32,7 +32,7 @@ export function ActionCard({
|
|||
onClick={disabled ? undefined : onClick}
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
"h-fit p-3 rounded-2xl text-left transition-all",
|
||||
"h-fit p-3 rounded-2xl text-left transition-all min-h-[56px]",
|
||||
disabled
|
||||
? "bg-white dark:bg-gray-800 border border-[#EAECF0] dark:border-gray-700 opacity-50 cursor-not-allowed"
|
||||
: isSelected
|
||||
|
|
|
|||
|
|
@ -49,9 +49,9 @@ export const CreateTabloModal = ({ onClose, onCreate }: CreateTabloModalProps) =
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/80 flex items-center justify-center z-50">
|
||||
<div className="fixed inset-0 bg-black/80 flex items-end sm:items-center justify-center z-50">
|
||||
<ClickOutside onClickOutside={handleClose}>
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-xl p-6 w-full max-w-4xl min-w-96 mx-4">
|
||||
<div className="bg-white dark:bg-gray-800 rounded-t-2xl sm:rounded-lg shadow-xl p-4 sm:p-6 w-full sm:max-w-4xl sm:mx-4 max-h-[90vh] overflow-y-auto">
|
||||
<h2 className="text-xl font-bold text-gray-900 dark:text-white mb-4">
|
||||
{t("modals:createTablo.title")}
|
||||
</h2>
|
||||
|
|
@ -81,17 +81,17 @@ export const CreateTabloModal = ({ onClose, onCreate }: CreateTabloModalProps) =
|
|||
</div>
|
||||
|
||||
{/* Modal Actions */}
|
||||
<div className="flex justify-end space-x-3 mt-6">
|
||||
<div className="flex flex-col-reverse sm:flex-row justify-end gap-2 sm:gap-3 mt-6">
|
||||
<button
|
||||
type="button"
|
||||
className="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-md"
|
||||
className="px-4 py-3 sm:py-2.5 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-lg min-h-[44px]"
|
||||
onClick={handleClose}
|
||||
>
|
||||
{t("common:buttons.cancel")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-md disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
className="px-4 py-3 sm:py-2.5 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed min-h-[44px]"
|
||||
onClick={handleCreate}
|
||||
disabled={!newTabloName.trim() || creationMode === "image"}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export function DashboardActionCards({
|
|||
const { t } = useTranslation("pages");
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-5">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3 sm:gap-4 mb-5">
|
||||
<ActionCard
|
||||
icon={<FolderPlus className="w-6 h-6" />}
|
||||
label={t("dashboard.actionCards.createProject.label")}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ function TaskRow({
|
|||
|
||||
return (
|
||||
<div
|
||||
className="grid grid-cols-[auto_1fr_1fr_auto_auto] items-center gap-4 px-4 py-3 hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors border-b border-gray-200 dark:border-gray-700 cursor-pointer"
|
||||
className="flex items-center gap-3 px-4 py-3 hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors border-b border-gray-200 dark:border-gray-700 cursor-pointer"
|
||||
onClick={() => {
|
||||
if (task.tablos) {
|
||||
navigate(`/tablos/${task.tablos.id}?section=tasks`);
|
||||
|
|
@ -66,7 +66,7 @@ function TaskRow({
|
|||
{/* Checkbox */}
|
||||
<button
|
||||
className={cn(
|
||||
"w-6 h-6 rounded-full border-2 flex items-center justify-center shrink-0",
|
||||
"w-8 h-8 min-w-[32px] rounded-full border-2 flex items-center justify-center shrink-0",
|
||||
isDone
|
||||
? "bg-purple-600 border-purple-600"
|
||||
: "border-gray-300 hover:border-purple-400 dark:border-gray-600 dark:hover:border-purple-500"
|
||||
|
|
@ -79,48 +79,48 @@ function TaskRow({
|
|||
{isDone && <CheckCircle2 className="w-4 h-4 text-white" />}
|
||||
</button>
|
||||
|
||||
{/* Title */}
|
||||
<p
|
||||
className={cn(
|
||||
"text-sm font-medium truncate",
|
||||
isDone
|
||||
? "line-through text-gray-400 dark:text-gray-500"
|
||||
: "text-gray-900 dark:text-gray-100"
|
||||
)}
|
||||
>
|
||||
{task.title}
|
||||
</p>
|
||||
|
||||
{/* Tablo */}
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
{task.tablos && (
|
||||
<>
|
||||
<div
|
||||
className={cn(
|
||||
"w-6 h-6 rounded-lg flex items-center justify-center text-xs shrink-0",
|
||||
task.tablos.color || "bg-gray-400"
|
||||
)}
|
||||
>
|
||||
<span className="text-white font-bold text-[10px]">
|
||||
{task.tablos.name.charAt(0).toUpperCase()}
|
||||
{/* Title + Tablo (stacked on mobile) */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<p
|
||||
className={cn(
|
||||
"text-sm font-medium truncate",
|
||||
isDone
|
||||
? "line-through text-gray-400 dark:text-gray-500"
|
||||
: "text-gray-900 dark:text-gray-100"
|
||||
)}
|
||||
>
|
||||
{task.title}
|
||||
</p>
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
{task.tablos && (
|
||||
<>
|
||||
<div
|
||||
className={cn(
|
||||
"w-4 h-4 rounded flex items-center justify-center text-xs shrink-0",
|
||||
task.tablos.color || "bg-gray-400"
|
||||
)}
|
||||
>
|
||||
<span className="text-white font-bold text-[8px]">
|
||||
{task.tablos.name.charAt(0).toUpperCase()}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-xs text-gray-500 dark:text-gray-400 truncate">
|
||||
{task.tablos.name}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300 hidden sm:inline truncate">
|
||||
{task.tablos.name}
|
||||
</>
|
||||
)}
|
||||
{formattedDate && (
|
||||
<span className="text-xs text-gray-400 dark:text-gray-500 hidden sm:inline whitespace-nowrap">
|
||||
{formattedDate}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Date */}
|
||||
<span className="text-sm text-gray-500 dark:text-gray-400 hidden md:inline whitespace-nowrap">
|
||||
{formattedDate}
|
||||
</span>
|
||||
|
||||
{/* Status badge */}
|
||||
<span
|
||||
className={cn(
|
||||
"px-3 py-1 rounded-full text-xs font-medium whitespace-nowrap",
|
||||
"px-2 sm:px-3 py-1 rounded-full text-xs font-medium whitespace-nowrap shrink-0",
|
||||
badge.className
|
||||
)}
|
||||
>
|
||||
|
|
@ -151,24 +151,22 @@ export function DashboardTaskList() {
|
|||
return (
|
||||
<>
|
||||
<div className="bg-white dark:bg-gray-800 rounded-2xl border border-gray-100 dark:border-gray-700">
|
||||
<div className="flex items-center justify-between px-4 py-5 border-b border-gray-200 dark:border-gray-700">
|
||||
<h2 className="text-2xl font-semibold text-gray-900 dark:text-gray-100">
|
||||
<div className="flex items-center justify-between px-4 py-4 sm:py-5 border-b border-gray-200 dark:border-gray-700">
|
||||
<h2 className="text-xl sm:text-2xl font-semibold text-gray-900 dark:text-gray-100">
|
||||
{t("dashboard.taskList.title")}
|
||||
</h2>
|
||||
<button
|
||||
className="flex items-center gap-2 px-4 py-2 bg-white dark:bg-gray-700 rounded-lg border border-gray-200 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-300"
|
||||
className="flex items-center gap-2 px-4 py-2.5 bg-white dark:bg-gray-700 rounded-lg border border-gray-200 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-300 min-h-[44px]"
|
||||
onClick={() => setIsTaskModalOpen(true)}
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
<span>{t("dashboard.taskList.addTask")}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="overflow-x-auto">
|
||||
<div className="min-w-[600px]">
|
||||
{myTasks.map((task) => (
|
||||
<TaskRow key={task.id} task={task} onToggleDone={handleToggleDone} />
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
{myTasks.map((task) => (
|
||||
<TaskRow key={task.id} task={task} onToggleDone={handleToggleDone} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ export const DeleteTabloModal = ({
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/60 flex items-center justify-center z-50">
|
||||
<div className="fixed inset-0 bg-black/60 flex items-end sm:items-center justify-center z-50">
|
||||
<ClickOutside onClickOutside={onClose}>
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-xl p-6 w-full max-w-md mx-4">
|
||||
<div className="bg-white dark:bg-gray-800 rounded-t-2xl sm:rounded-lg shadow-xl p-4 sm:p-6 w-full sm:max-w-md sm:mx-4">
|
||||
{/* Header */}
|
||||
<div className="flex items-center mb-4">
|
||||
<div className="w-12 h-12 bg-red-100 dark:bg-red-900/20 rounded-full flex items-center justify-center mr-4">
|
||||
|
|
@ -70,10 +70,10 @@ export const DeleteTabloModal = ({
|
|||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex justify-end space-x-3">
|
||||
<div className="flex flex-col-reverse sm:flex-row justify-end gap-2 sm:gap-3">
|
||||
<button
|
||||
type="button"
|
||||
className="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-md transition-colors"
|
||||
className="px-4 py-3 sm:py-2.5 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-lg transition-colors min-h-[44px]"
|
||||
onClick={onClose}
|
||||
disabled={isDeleting}
|
||||
>
|
||||
|
|
@ -81,7 +81,7 @@ export const DeleteTabloModal = ({
|
|||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="px-4 py-2 text-sm font-medium text-white bg-red-600 hover:bg-red-700 rounded-md transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
|
||||
className="px-4 py-3 sm:py-2.5 text-sm font-medium text-white bg-red-600 hover:bg-red-700 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2 min-h-[44px]"
|
||||
onClick={handleConfirm}
|
||||
disabled={isDeleting}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ export function ProjectCardList({
|
|||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6">
|
||||
{visibleTablos.map((tablo) => (
|
||||
<ProjectCard
|
||||
key={tablo.id}
|
||||
|
|
@ -54,7 +54,7 @@ export function ProjectCardList({
|
|||
{hasMore && (
|
||||
<div className="flex justify-center mt-6">
|
||||
<button
|
||||
className="flex items-center gap-1.5 text-purple-600 hover:text-purple-500 dark:text-purple-400 dark:hover:text-purple-300 font-medium text-sm"
|
||||
className="flex items-center gap-1.5 text-purple-600 hover:text-purple-500 dark:text-purple-400 dark:hover:text-purple-300 font-medium text-sm min-h-[44px] px-4"
|
||||
onClick={() => setExpanded((prev) => !prev)}
|
||||
>
|
||||
{expanded ? (
|
||||
|
|
|
|||
|
|
@ -142,8 +142,8 @@ export const TaskModal = ({
|
|||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
||||
<div className="bg-card border border-border rounded-lg p-6 w-full max-w-md shadow-xl">
|
||||
<div className="fixed inset-0 bg-black/50 flex items-end sm:items-center justify-center z-50">
|
||||
<div className="bg-card border border-border rounded-t-2xl sm:rounded-lg p-4 sm:p-6 w-full sm:max-w-md shadow-xl max-h-[90vh] overflow-y-auto">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<TypographyH2 className="text-xl font-bold text-foreground">
|
||||
|
|
|
|||
|
|
@ -399,10 +399,10 @@ export const TabloDetailsPage = () => {
|
|||
<h1 className="text-xl md:text-3xl font-bold text-foreground">{tablo.name}</h1>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<div className="flex items-center gap-3 w-full sm:w-auto">
|
||||
<Link
|
||||
to={`/chat/${tabloId}`}
|
||||
className="bg-[#804EEC] hover:bg-[#6f3fd4] text-white font-medium py-2 px-4 rounded-lg flex items-center gap-2 transition-colors"
|
||||
className="bg-[#804EEC] hover:bg-[#6f3fd4] text-white font-medium py-2.5 px-4 rounded-lg flex items-center justify-center gap-2 transition-colors flex-1 sm:flex-none min-h-[44px]"
|
||||
>
|
||||
<MessageCircleIcon className="w-5 h-5" />
|
||||
Discussion
|
||||
|
|
@ -411,7 +411,7 @@ export const TabloDetailsPage = () => {
|
|||
<button
|
||||
type="button"
|
||||
onClick={() => setIsShareDialogOpen(true)}
|
||||
className="border border-[#804EEC] text-[#804EEC] hover:bg-[#804EEC]/10 font-medium py-2 px-4 rounded-lg flex items-center gap-2 transition-colors"
|
||||
className="border border-[#804EEC] text-[#804EEC] hover:bg-[#804EEC]/10 font-medium py-2.5 px-4 rounded-lg flex items-center justify-center gap-2 transition-colors flex-1 sm:flex-none min-h-[44px]"
|
||||
>
|
||||
<UserPlusIcon className="w-5 h-5" />
|
||||
Inviter
|
||||
|
|
@ -421,12 +421,12 @@ export const TabloDetailsPage = () => {
|
|||
</div>
|
||||
|
||||
{/* ── Metadata bar ──────────────────────────────────────────────── */}
|
||||
<div className="flex flex-wrap items-center gap-6 text-sm border-b border-[#F2F4F7] dark:border-gray-700 pb-4 mb-4">
|
||||
<div className="flex items-center gap-2 md:border-r border-[#D0D5DD] dark:border-gray-600 pr-4">
|
||||
<div className="flex flex-wrap items-center gap-3 sm:gap-6 text-sm border-b border-[#F2F4F7] dark:border-gray-700 pb-4 mb-4">
|
||||
<div className="flex items-center gap-2 sm:border-r border-[#D0D5DD] dark:border-gray-600 sm:pr-4">
|
||||
<span className="text-muted-foreground">Rôle :</span>
|
||||
<span className="text-foreground font-medium">{isAdmin ? "Admin" : "Invité"}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 md:border-r border-[#D0D5DD] dark:border-gray-600 pr-4">
|
||||
<div className="flex items-center gap-2 sm:border-r border-[#D0D5DD] dark:border-gray-600 sm:pr-4">
|
||||
<span className="text-muted-foreground">Créé le :</span>
|
||||
<span className="text-foreground">
|
||||
{new Intl.DateTimeFormat("fr-FR", {
|
||||
|
|
@ -436,7 +436,7 @@ export const TabloDetailsPage = () => {
|
|||
}).format(new Date(tablo.created_at))}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 md:border-r border-[#D0D5DD] dark:border-gray-600 pr-4">
|
||||
<div className="flex items-center gap-2 sm:border-r border-[#D0D5DD] dark:border-gray-600 sm:pr-4">
|
||||
<span className="text-muted-foreground">Statut :</span>
|
||||
<span className={cn("px-3 py-1 rounded-full text-xs font-medium", badgeClass)}>
|
||||
{statusLabel}
|
||||
|
|
@ -462,7 +462,7 @@ export const TabloDetailsPage = () => {
|
|||
{/* ── Tab navigation ──────────────────────────────────────────────── */}
|
||||
<div className="w-full bg-white dark:bg-background sticky top-0 z-40">
|
||||
<div className="px-4 py-2">
|
||||
<div className="flex flex-wrap items-center gap-6 mb-4 border-b border-[#F2F4F7] dark:border-gray-700">
|
||||
<div className="flex items-center gap-4 sm:gap-6 mb-4 border-b border-[#F2F4F7] dark:border-gray-700 overflow-x-auto scrollbar-none -mx-4 px-4">
|
||||
{TABS.map((tab) => {
|
||||
const isActive = activeSection === tab.id;
|
||||
return (
|
||||
|
|
@ -472,7 +472,7 @@ export const TabloDetailsPage = () => {
|
|||
disabled={tab.disabled}
|
||||
onClick={() => !tab.disabled && setSearchParams({ section: tab.id })}
|
||||
className={cn(
|
||||
"flex items-center gap-2 pb-3 px-1 text-sm font-semibold transition-colors border-b-2",
|
||||
"flex items-center gap-2 pb-3 px-1 text-sm font-semibold transition-colors border-b-2 shrink-0 min-h-[44px]",
|
||||
isActive
|
||||
? "text-[#804EEC] border-[#804EEC]"
|
||||
: "text-[#667085] border-transparent hover:text-gray-900 dark:hover:text-gray-100",
|
||||
|
|
@ -824,13 +824,13 @@ export const TabloDetailsPage = () => {
|
|||
|
||||
<div className="space-y-4">
|
||||
{/* Invite Input */}
|
||||
<div className="flex space-x-2">
|
||||
<div className="flex flex-col sm:flex-row gap-2">
|
||||
<Input
|
||||
type="email"
|
||||
value={inviteEmail}
|
||||
onChange={(e) => setInviteEmail(e.target.value)}
|
||||
placeholder="Email de l'utilisateur"
|
||||
className="flex-1"
|
||||
className="flex-1 min-h-[44px]"
|
||||
/>
|
||||
{isInvitingUser ? (
|
||||
<div className="flex justify-center items-center px-4">
|
||||
|
|
@ -1027,7 +1027,7 @@ function EtapesSection({
|
|||
return (
|
||||
<div className="space-y-4">
|
||||
{isAdmin && (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-2">
|
||||
<Input
|
||||
value={newEtapeTitle}
|
||||
onChange={(event) => setNewEtapeTitle(event.target.value)}
|
||||
|
|
@ -1037,11 +1037,12 @@ function EtapesSection({
|
|||
void handleAddEtape();
|
||||
}
|
||||
}}
|
||||
className="h-9 sm:w-80"
|
||||
className="h-11 sm:h-9 sm:w-80"
|
||||
/>
|
||||
<Button
|
||||
onClick={() => void handleAddEtape()}
|
||||
disabled={isCreatingEtape || !newEtapeTitle.trim()}
|
||||
className="min-h-[44px] sm:min-h-0"
|
||||
>
|
||||
<PlusIcon className="w-4 h-4" />
|
||||
Ajouter une étape
|
||||
|
|
@ -1085,7 +1086,7 @@ function EtapesSection({
|
|||
<button
|
||||
type="button"
|
||||
onClick={() => toggleEtape(etape.id)}
|
||||
className="w-full flex items-center gap-4 px-5 py-4 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors text-left"
|
||||
className="w-full flex items-center gap-3 sm:gap-4 px-3 sm:px-5 py-4 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors text-left min-h-[56px]"
|
||||
>
|
||||
{isExpanded ? (
|
||||
<ChevronDownIcon className="w-5 h-5 text-gray-400 shrink-0" />
|
||||
|
|
@ -1100,58 +1101,60 @@ function EtapesSection({
|
|||
</div>
|
||||
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="font-semibold text-gray-900 dark:text-gray-100 truncate">
|
||||
<h3 className="font-semibold text-gray-900 dark:text-gray-100 truncate text-sm sm:text-base">
|
||||
{etape.title}
|
||||
</h3>
|
||||
{etape.description && (
|
||||
<p className="text-sm text-muted-foreground truncate mt-0.5">
|
||||
<p className="text-xs sm:text-sm text-muted-foreground truncate mt-0.5">
|
||||
{etape.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{etape.due_date && (
|
||||
<div
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
{etape.due_date && (
|
||||
<div
|
||||
className={cn(
|
||||
"items-center gap-1 text-xs hidden sm:flex",
|
||||
derivedStatus !== "done" &&
|
||||
new Date(etape.due_date) < new Date(new Date().toDateString())
|
||||
? "text-red-500"
|
||||
: "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
<CalendarIcon className="w-3.5 h-3.5" />
|
||||
<span>
|
||||
{new Intl.DateTimeFormat("fr-FR", {
|
||||
day: "2-digit",
|
||||
month: "short",
|
||||
}).format(new Date(etape.due_date))}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<span
|
||||
className={cn(
|
||||
"flex items-center gap-1 text-xs shrink-0",
|
||||
derivedStatus !== "done" &&
|
||||
new Date(etape.due_date) < new Date(new Date().toDateString())
|
||||
? "text-red-500"
|
||||
: "text-muted-foreground"
|
||||
"px-2 sm:px-2.5 py-1 rounded-full text-[10px] sm:text-xs font-medium",
|
||||
status.color
|
||||
)}
|
||||
>
|
||||
<CalendarIcon className="w-3.5 h-3.5" />
|
||||
<span>
|
||||
{new Intl.DateTimeFormat("fr-FR", {
|
||||
day: "2-digit",
|
||||
month: "short",
|
||||
}).format(new Date(etape.due_date))}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{status.label}
|
||||
</span>
|
||||
|
||||
<span
|
||||
className={cn(
|
||||
"px-2.5 py-1 rounded-full text-xs font-medium shrink-0",
|
||||
status.color
|
||||
)}
|
||||
>
|
||||
{status.label}
|
||||
</span>
|
||||
|
||||
{totalCount > 0 && (
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
<div className="w-16 h-1.5 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-green-500 rounded-full transition-all"
|
||||
style={{ width: `${progressPct}%` }}
|
||||
/>
|
||||
{totalCount > 0 && (
|
||||
<div className="hidden sm:flex items-center gap-2">
|
||||
<div className="w-16 h-1.5 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-green-500 rounded-full transition-all"
|
||||
style={{ width: `${progressPct}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-xs text-muted-foreground whitespace-nowrap">
|
||||
{doneCount}/{totalCount}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-xs text-muted-foreground whitespace-nowrap">
|
||||
{doneCount}/{totalCount}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{/* Child tasks + add task */}
|
||||
|
|
@ -1162,7 +1165,7 @@ function EtapesSection({
|
|||
{childTasks.map((task) => (
|
||||
<div
|
||||
key={task.id}
|
||||
className="flex items-center gap-3 px-5 py-3 pl-16 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
|
||||
className="flex items-center gap-3 px-3 sm:px-5 py-3 pl-8 sm:pl-16 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
|
||||
>
|
||||
{task.status === "done" ? (
|
||||
<CircleCheckIcon className="w-4 h-4 text-green-500 shrink-0" />
|
||||
|
|
@ -1214,14 +1217,14 @@ function EtapesSection({
|
|||
)}
|
||||
|
||||
{childTasks.length === 0 && addingTaskToEtape !== etape.id && (
|
||||
<div className="px-5 py-4 pl-16 text-sm text-muted-foreground">
|
||||
<div className="px-3 sm:px-5 py-4 pl-8 sm:pl-16 text-sm text-muted-foreground">
|
||||
Aucune tâche dans cette étape
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Inline add task */}
|
||||
{addingTaskToEtape === etape.id ? (
|
||||
<div className="flex items-center gap-2 px-5 py-3 pl-16 border-t border-gray-100 dark:border-gray-700">
|
||||
<div className="flex items-center gap-2 px-3 sm:px-5 py-3 pl-8 sm:pl-16 border-t border-gray-100 dark:border-gray-700">
|
||||
<div className="w-4 h-4 rounded-full border-2 border-gray-300 dark:border-gray-600 shrink-0" />
|
||||
<input
|
||||
autoFocus
|
||||
|
|
@ -1236,13 +1239,13 @@ function EtapesSection({
|
|||
}
|
||||
}}
|
||||
placeholder="Nom de la tâche..."
|
||||
className="flex-1 text-sm bg-transparent border-none outline-none text-gray-900 dark:text-gray-100 placeholder-gray-400"
|
||||
className="flex-1 text-sm bg-transparent border-none outline-none text-gray-900 dark:text-gray-100 placeholder-gray-400 min-w-0"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleAddTask(etape.id)}
|
||||
disabled={!newTaskTitle.trim()}
|
||||
className="text-xs font-medium px-3 py-1 rounded-md bg-[#804EEC] text-white hover:bg-[#6f3fd4] disabled:opacity-40 transition-colors"
|
||||
className="text-xs font-medium px-3 py-2 rounded-md bg-[#804EEC] text-white hover:bg-[#6f3fd4] disabled:opacity-40 transition-colors min-h-[36px] shrink-0"
|
||||
>
|
||||
Ajouter
|
||||
</button>
|
||||
|
|
@ -1252,7 +1255,7 @@ function EtapesSection({
|
|||
setAddingTaskToEtape(null);
|
||||
setNewTaskTitle("");
|
||||
}}
|
||||
className="text-xs text-muted-foreground hover:text-foreground px-2 py-1"
|
||||
className="text-xs text-muted-foreground hover:text-foreground px-2 py-2 min-h-[36px] shrink-0"
|
||||
>
|
||||
Annuler
|
||||
</button>
|
||||
|
|
@ -1265,7 +1268,7 @@ function EtapesSection({
|
|||
setAddingTaskToEtape(etape.id);
|
||||
setNewTaskTitle("");
|
||||
}}
|
||||
className="flex items-center gap-2 px-5 py-2.5 pl-16 text-sm text-muted-foreground hover:text-[#804EEC] hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors w-full text-left border-t border-gray-100 dark:border-gray-700"
|
||||
className="flex items-center gap-2 px-3 sm:px-5 py-3 pl-8 sm:pl-16 text-sm text-muted-foreground hover:text-[#804EEC] hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors w-full text-left border-t border-gray-100 dark:border-gray-700 min-h-[44px]"
|
||||
>
|
||||
<PlusIcon className="w-4 h-4" />
|
||||
Ajouter une tâche
|
||||
|
|
|
|||
|
|
@ -381,7 +381,7 @@ export const TabloPage = () => {
|
|||
}}
|
||||
>
|
||||
<div
|
||||
className={`bg-card rounded-lg shadow-lg transition-all duration-300 w-56 overflow-hidden border border-border ${
|
||||
className={`bg-card rounded-lg shadow-lg transition-all duration-300 w-full overflow-hidden border border-border ${
|
||||
isAdmin ? "hover:shadow-xl cursor-pointer" : "hover:shadow-xl cursor-pointer opacity-75"
|
||||
}`}
|
||||
onClick={(e) => {
|
||||
|
|
@ -430,11 +430,11 @@ export const TabloPage = () => {
|
|||
<span>{getUserRole(tablo)}</span>
|
||||
</div>
|
||||
{/* Action buttons */}
|
||||
<div className="flex items-center gap-1 pt-1">
|
||||
<div className="flex items-center gap-2 pt-1">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon-sm"
|
||||
className="p-1.5"
|
||||
className="min-w-[44px] min-h-[44px] p-2"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
navigate(`/chat/${tablo.id}`);
|
||||
|
|
@ -446,7 +446,7 @@ export const TabloPage = () => {
|
|||
<Button
|
||||
variant="outline"
|
||||
size="icon-sm"
|
||||
className="p-1.5"
|
||||
className="min-w-[44px] min-h-[44px] p-2"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
navigate(`/tablos/${tablo.id}?section=members`);
|
||||
|
|
@ -458,7 +458,7 @@ export const TabloPage = () => {
|
|||
<Button
|
||||
variant="outline"
|
||||
size="icon-sm"
|
||||
className="p-1.5 text-destructive hover:text-destructive"
|
||||
className="min-w-[44px] min-h-[44px] p-2 text-destructive hover:text-destructive"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleDeleteTablo(tablo.id);
|
||||
|
|
@ -509,12 +509,12 @@ export const TabloPage = () => {
|
|||
setContextMenuPosition(null);
|
||||
}}
|
||||
>
|
||||
<header className="px-6 pt-6 pb-4">
|
||||
<p className="text-base text-[#475467] dark:text-gray-400 mb-2 font-medium">
|
||||
<header className="px-4 sm:px-6 pt-6 pb-4">
|
||||
<p className="text-sm sm:text-base text-[#475467] dark:text-gray-400 mb-2 font-medium">
|
||||
{formattedDate}
|
||||
</p>
|
||||
<div className="flex items-center gap-3 flex-wrap">
|
||||
<h1 className="text-[24px] font-medium text-[#475467] dark:text-gray-400">
|
||||
<div className="flex items-center justify-between flex-wrap gap-4">
|
||||
<h1 className="text-xl sm:text-[24px] font-medium text-[#475467] dark:text-gray-400">
|
||||
{getGreeting()},{" "}
|
||||
<span className="text-gray-900 dark:text-gray-100 font-medium">
|
||||
{user.first_name ?? user.name}
|
||||
|
|
@ -656,10 +656,10 @@ export const TabloPage = () => {
|
|||
onSendMessage={() => navigate("/chat")}
|
||||
/>
|
||||
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<div className="py-8">
|
||||
{filteredTablos && filteredTablos.length > 0 ? (
|
||||
viewMode === "grid" ? (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
||||
{filteredTablos.map((tablo) => renderTablo(tablo))}
|
||||
</div>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ function TabloCard({
|
|||
</span>
|
||||
<button
|
||||
type="button"
|
||||
className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 transition-colors"
|
||||
className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 transition-colors p-2 -m-2 min-w-[44px] min-h-[44px] flex items-center justify-center"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDelete(tablo.id);
|
||||
|
|
@ -262,7 +262,7 @@ function TabloRow({
|
|||
<td className="px-6 py-4 text-right">
|
||||
<button
|
||||
type="button"
|
||||
className="text-gray-400 hover:text-red-500 dark:text-gray-500 dark:hover:text-red-400 transition-colors p-1 rounded"
|
||||
className="text-gray-400 hover:text-red-500 dark:text-gray-500 dark:hover:text-red-400 transition-colors p-2 rounded min-w-[44px] min-h-[44px] inline-flex items-center justify-center"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDelete(tablo.id);
|
||||
|
|
@ -317,7 +317,7 @@ export function TablosPage() {
|
|||
<button
|
||||
type="button"
|
||||
onClick={() => setShowCreateModal(true)}
|
||||
className="bg-purple-600 hover:bg-purple-700 text-white px-6 py-3 rounded-lg font-medium flex items-center justify-center gap-2 transition-colors"
|
||||
className="bg-purple-600 hover:bg-purple-700 text-white px-6 py-3 rounded-lg font-medium flex items-center justify-center gap-2 transition-colors w-full sm:w-auto min-h-[44px]"
|
||||
>
|
||||
<PlusIcon className="w-5 h-5" />
|
||||
{t("tablo.createButton")}
|
||||
|
|
@ -397,7 +397,7 @@ export function TablosPage() {
|
|||
</p>
|
||||
</div>
|
||||
) : viewMode === "card" ? (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 sm:gap-6">
|
||||
{filteredTablos.map((tablo) => (
|
||||
<TabloCard
|
||||
key={tablo.id}
|
||||
|
|
@ -408,8 +408,8 @@ export function TablosPage() {
|
|||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-white dark:bg-gray-800 rounded-xl border border-[#EAECF0] dark:border-gray-700 overflow-hidden">
|
||||
<table className="w-full">
|
||||
<div className="bg-white dark:bg-gray-800 rounded-xl border border-[#EAECF0] dark:border-gray-700 overflow-x-auto -mx-4 sm:mx-0">
|
||||
<table className="w-full min-w-[600px]">
|
||||
<thead className="bg-gray-50 dark:bg-gray-800/80 border-b border-[#EAECF0] dark:border-gray-700">
|
||||
<tr>
|
||||
<th className="px-6 py-3 text-left text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider">
|
||||
|
|
|
|||
Loading…
Reference in a new issue