fix(kanban): responsive layout, drag-drop offset, and project view enhancements

Kanban pipeline board:
- Fix h-full flex-col layout so board fills viewport height on all screen sizes
- KanbanColumn drop zone: flex-1 min-h-0 so it expands to fill full column height
- KanbanCard: extract KanbanCardDisplay as named export for DragOverlay rendering above overflow containers
- KanbanPage: lift DndContext to page root; add "Add Stage" shortcut to page header
- PageTransition: remove slide-in-from-bottom-4 transform — the CSS transform ancestor was creating a containing block for position:fixed DragOverlay, causing drag ghost to render at a viewport offset
- Migrate fireCampaignToast to toast.custom() Sonner v2 API with explicit card styling

Project & lead views:
- LeadProjectPage: enrich project detail with richer info sections aligned with project stage
- OwnerProjectDetail: expand construction project detail with additional data panels
- OwnerProjectList: project list view improvements
- mockStore: extend lead/project data to support richer project detail rendering
This commit is contained in:
Satyam-Rastogi
2026-04-08 23:40:45 +05:30
parent b1ece35ac8
commit c584d918ac
8 changed files with 2290 additions and 384 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ const PageTransition = ({ children }) => {
return (
<div
key={location.pathname}
className="h-full animate-in fade-in slide-in-from-bottom-4 duration-500 ease-out fill-mode-forwards"
className="h-full animate-in fade-in duration-300 ease-out"
>
{children}
</div>
+32 -21
View File
@@ -29,33 +29,23 @@ function avatarColor(name) {
return AVATAR_COLORS[idx];
}
export default function KanbanCard({ lead, columnColor, onClick, isDragOverlay }) {
const { attributes, listeners, setNodeRef, transform, isDragging } = useDraggable({
id: lead.id,
data: { lead },
});
const style = {
transform: CSS.Translate.toString(transform),
opacity: isDragging ? 0.35 : 1,
zIndex: isDragging ? 999 : 'auto',
};
// ── Pure visual display — no dnd bindings. Used both inside KanbanCard and
// inside DragOverlay (so the dragged clone renders above overflow containers).
export function KanbanCardDisplay({ lead, columnColor, isOverlay = false, onClick, forwardRef, dragProps = {} }) {
const days = getDaysInStage(lead.activity);
const initials = getInitials(lead.name);
const avatarCls = avatarColor(lead.name);
return (
<div
ref={isDragOverlay ? undefined : setNodeRef}
style={isDragOverlay ? undefined : style}
{...(isDragOverlay ? {} : { ...attributes, ...listeners })}
onClick={isDragOverlay ? undefined : onClick}
className={`group relative bg-white dark:bg-zinc-900 border rounded-xl cursor-grab active:cursor-grabbing transition-all select-none
${isDragOverlay
? 'shadow-2xl border-zinc-200 dark:border-white/20 rotate-1 scale-[1.02]'
: 'border-zinc-200 dark:border-white/[0.07] hover:shadow-md dark:hover:shadow-black/30 hover:border-zinc-300 dark:hover:border-white/[0.14]'
}`}
ref={forwardRef}
{...dragProps}
onClick={onClick}
className={`group relative bg-white dark:bg-zinc-900 border rounded-xl select-none transition-[border-color,box-shadow] ${
isOverlay
? 'border-blue-400 dark:border-blue-500 rotate-1 shadow-[0_20px_40px_rgba(0,0,0,0.35)] cursor-grabbing scale-[1.03]'
: 'border-zinc-200 dark:border-white/[0.07] cursor-grab active:cursor-grabbing hover:shadow-md dark:hover:shadow-black/30 hover:border-zinc-300 dark:hover:border-white/[0.14]'
}`}
>
{/* Left color accent bar */}
<div
@@ -112,3 +102,24 @@ export default function KanbanCard({ lead, columnColor, onClick, isDragOverlay }
</div>
);
}
// ── Draggable wrapper — ghost stays frozen in place (no transform applied),
// DragOverlay handles all visual movement above overflow containers.
export default function KanbanCard({ lead, columnColor, onClick }) {
const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
id: lead.id,
data: { lead, columnColor },
});
return (
<div
ref={setNodeRef}
style={{ opacity: isDragging ? 0.3 : 1 }}
{...attributes}
{...listeners}
onClick={isDragging ? undefined : onClick}
>
<KanbanCardDisplay lead={lead} columnColor={columnColor} />
</div>
);
}
+7 -15
View File
@@ -1,7 +1,7 @@
import React from 'react';
import { useDroppable } from '@dnd-kit/core';
import { motion, AnimatePresence } from 'framer-motion';
import { Plus, Settings, Trash2, Pencil } from 'lucide-react';
import { Trash2, Pencil } from 'lucide-react';
import KanbanCard from './KanbanCard';
export default function KanbanColumn({
@@ -16,9 +16,9 @@ export default function KanbanColumn({
const isBucket = column.type === 'bucket';
return (
<div className="flex flex-col shrink-0 w-[272px]">
<div className="flex flex-col shrink-0 w-[272px] h-full">
{/* Column header */}
<div className="mb-3">
<div className="shrink-0 mb-3">
<div className="flex items-center justify-between gap-2 px-1">
<div className="flex items-center gap-2 min-w-0">
<div className="w-2 h-2 rounded-full shrink-0" style={{ backgroundColor: column.color }} />
@@ -72,16 +72,17 @@ export default function KanbanColumn({
)}
</div>
{/* Drop zone */}
{/* Drop zone — flex-1 + min-h-0 so it fills remaining column height */}
<div
ref={setNodeRef}
className={`group flex-1 min-h-[120px] rounded-2xl p-2 transition-all duration-150 ${
className={`group flex-1 min-h-0 rounded-2xl transition-all duration-150 ${
isOver
? 'bg-blue-50/80 dark:bg-blue-500/10 ring-2 ring-inset ring-blue-400/40'
: 'bg-zinc-100/60 dark:bg-zinc-800/30'
}`}
>
<div className="flex flex-col gap-2">
{/* Inner scroll container — cards scroll within the column */}
<div className="flex flex-col gap-2 p-2 h-full overflow-y-auto overflow-x-hidden scrollbar-thin scrollbar-thumb-zinc-300 dark:scrollbar-thumb-zinc-700 scrollbar-track-transparent">
<AnimatePresence initial={false}>
{leads.map((lead, i) => (
<motion.div
@@ -109,15 +110,6 @@ export default function KanbanColumn({
</div>
</div>
{/* Add column button after last stage column */}
{isLast && canManage && (
<button
onClick={onAddColumn}
className="mt-3 flex items-center gap-1.5 px-3 py-2 rounded-xl border border-dashed border-zinc-300 dark:border-zinc-700 text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-300 hover:border-zinc-400 dark:hover:border-zinc-500 text-xs font-semibold transition-colors"
>
<Plus size={12} /> Add Stage
</button>
)}
</div>
);
}