Compare commits
8 Commits
0b7a1a72d3
...
front
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96ae1ef37c | ||
|
|
461e9a8105 | ||
|
|
cb73cd219b | ||
|
|
0d92ea9893 | ||
|
|
1a675834fe | ||
|
|
9f7e91d0a9 | ||
|
|
69b2571dd4 | ||
|
|
bfd8c04a90 |
BIN
taskncoffee-app/public/images/a.jpg
Normal file
BIN
taskncoffee-app/public/images/a.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 399 KiB |
@@ -5,7 +5,7 @@ import { AuthLayout } from './layouts/AuthLayout'
|
|||||||
import { Routes, Route, Navigate } from 'react-router'
|
import { Routes, Route, Navigate } from 'react-router'
|
||||||
import Dashboard from './pages/Dashboard'
|
import Dashboard from './pages/Dashboard'
|
||||||
import RootRedirect from './pages/RootRedirect'
|
import RootRedirect from './pages/RootRedirect'
|
||||||
|
import { TaskButton } from './components/Card'
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
|
||||||
@@ -23,6 +23,10 @@ function App() {
|
|||||||
<Route path="/dashboard" element={
|
<Route path="/dashboard" element={
|
||||||
<Dashboard />
|
<Dashboard />
|
||||||
} />
|
} />
|
||||||
|
<Route path="/card" element={<TaskButton />} />
|
||||||
|
<Route path="/card/:id" element={
|
||||||
|
<TaskButton />
|
||||||
|
} />
|
||||||
<Route path="*" element={<Navigate to="/auth/login" replace />} />
|
<Route path="*" element={<Navigate to="/auth/login" replace />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export default function Calendar() {
|
|||||||
|
|
||||||
|
|
||||||
const daysOfWeek = [
|
const daysOfWeek = [
|
||||||
'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'
|
'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'
|
||||||
].map((name, index) => {
|
].map((name, index) => {
|
||||||
const date = new Date(currentDate);
|
const date = new Date(currentDate);
|
||||||
date.setDate(currentDate.getDate() + index);
|
date.setDate(currentDate.getDate() + index);
|
||||||
@@ -90,7 +90,8 @@ export default function Calendar() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
"bg-card/30 backdrop-blur-md p-6 rounded-2xl flex-[3]",
|
styles.utility.glass,
|
||||||
|
"p-6 rounded-2xl flex-3",
|
||||||
styles.utility.transitionSlow
|
styles.utility.transitionSlow
|
||||||
)}>
|
)}>
|
||||||
<div className="flex gap-3 h-full overflow-x-auto px-3">
|
<div className="flex gap-3 h-full overflow-x-auto px-3">
|
||||||
@@ -101,7 +102,7 @@ export default function Calendar() {
|
|||||||
<div
|
<div
|
||||||
key={day.name}
|
key={day.name}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex-1 min-w-[150px] max-w-[180px] flex flex-col rounded-lg p-4 transition-all duration-300",
|
"flex-1 min-w-[180px] max-w-[200px] flex flex-col rounded-2xl p-4 transition-all duration-300",
|
||||||
isToday
|
isToday
|
||||||
? "bg-primary/10 border-2 border-primary neon-glow-soft"
|
? "bg-primary/10 border-2 border-primary neon-glow-soft"
|
||||||
: "bg-background border border-border"
|
: "bg-background border border-border"
|
||||||
@@ -157,9 +158,8 @@ export default function Calendar() {
|
|||||||
{/* Add button */}
|
{/* Add button */}
|
||||||
<button
|
<button
|
||||||
className={cn(
|
className={cn(
|
||||||
styles.button.icon,
|
styles.button.iconNeon,
|
||||||
"mt-2 w-full flex items-center justify-center gap-2",
|
"mt-2 w-full flex items-center justify-center gap-2",
|
||||||
styles.utility.glowSoft
|
|
||||||
)}
|
)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
console.log('Add task clicked for', day.name);
|
console.log('Add task clicked for', day.name);
|
||||||
|
|||||||
139
taskncoffee-app/src/components/Card.jsx
Normal file
139
taskncoffee-app/src/components/Card.jsx
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
import { useNavigate, useLocation } from 'react-router'
|
||||||
|
import styles from '@/lib/styles'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { X } from 'lucide-react'
|
||||||
|
|
||||||
|
|
||||||
|
export function TaskButton({ taskData }) {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const location = useLocation()
|
||||||
|
const [isModalOpen, setIsModalOpen] = useState(false)
|
||||||
|
|
||||||
|
const taskId = taskData?.id || 1
|
||||||
|
|
||||||
|
// Проверяем, открыта ли карточка по URL
|
||||||
|
useEffect(() => {
|
||||||
|
const isCardRoute = location.pathname === `/card/${taskId}`
|
||||||
|
setIsModalOpen(isCardRoute)
|
||||||
|
}, [location.pathname, taskId])
|
||||||
|
|
||||||
|
const openModal = () => {
|
||||||
|
navigate(`/card/${taskId}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
navigate(-1) // Возвращаемся на предыдущую страницу
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
key={taskData?.id || 1}
|
||||||
|
className={cn(
|
||||||
|
styles.card.task,
|
||||||
|
"w-[110px] cursor-pointer hover:opacity-80 transition-opacity"
|
||||||
|
)}
|
||||||
|
onClick={openModal}
|
||||||
|
>
|
||||||
|
{taskData?.title || "Sample task card"}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isModalOpen && (
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 p-4"
|
||||||
|
onClick={closeModal}
|
||||||
|
>
|
||||||
|
<div onClick={(e) => e.stopPropagation()}>
|
||||||
|
<CardComponent
|
||||||
|
status={taskData?.status}
|
||||||
|
time_spent={taskData?.time_spent}
|
||||||
|
description={taskData?.description}
|
||||||
|
title={taskData?.title}
|
||||||
|
priority={taskData?.priority}
|
||||||
|
due_date={taskData?.due_date}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CardComponent({
|
||||||
|
status = "todo",
|
||||||
|
time_spent = 0,
|
||||||
|
description = "No description",
|
||||||
|
title = "Task",
|
||||||
|
priority = "medium",
|
||||||
|
due_date = null
|
||||||
|
}) {
|
||||||
|
const formatTimeSpent = (minutes) => {
|
||||||
|
if (!minutes || minutes === 0) return "0 min";
|
||||||
|
const hours = Math.floor(minutes / 60);
|
||||||
|
const mins = minutes % 60;
|
||||||
|
if (hours > 0) {
|
||||||
|
return `${hours}h ${mins > 0 ? `${mins}m` : ''}`;
|
||||||
|
}
|
||||||
|
return `${mins}m`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatDate = (dateString) => {
|
||||||
|
if (!dateString) return "No due date";
|
||||||
|
const date = new Date(dateString);
|
||||||
|
return date.toLocaleDateString('en-US', {
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
year: 'numeric'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const priorityBadgeStyle = {
|
||||||
|
low: styles.badge.low,
|
||||||
|
medium: styles.badge.medium,
|
||||||
|
high: styles.badge.high,
|
||||||
|
critical: styles.badge.critical,
|
||||||
|
};
|
||||||
|
|
||||||
|
const statusLabels = {
|
||||||
|
open: "Open",
|
||||||
|
closed: "Closed",
|
||||||
|
in_progress: "In Progress",
|
||||||
|
todo: "To Do"
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cn(styles.card.filled, "flex flex-col w-full min-w-[400px] max-w-[600px] h-[400px] overflow-hidden")}>
|
||||||
|
<div className="flex items-end flex-1 -mt-6 -mx-6 bg-center bg-cover bg-[url('/images/a.jpg')]">
|
||||||
|
<h1 className={cn(styles.text.h1, "pl-6")}>{title}</h1>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 flex flex-col gap-3 z-10 mt-6">
|
||||||
|
<div className="flex items-center gap-2 flex-wrap">
|
||||||
|
<span className={cn(priorityBadgeStyle[priority])}>
|
||||||
|
{priority.charAt(0).toUpperCase() + priority.slice(1)}
|
||||||
|
</span>
|
||||||
|
<span className={cn(styles.text.smallMuted)}>
|
||||||
|
Status: {statusLabels[status]}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-4 text-sm">
|
||||||
|
<span className={cn(styles.text.smallMuted)}>
|
||||||
|
⏱️ {formatTimeSpent(time_spent)}
|
||||||
|
</span>
|
||||||
|
<span className={cn(styles.text.smallMuted)}>
|
||||||
|
📅 {formatDate(due_date)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className={cn(styles.text.smallMuted, "flex-1 line-clamp-3 overflow-auto")}>
|
||||||
|
{description}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<button className={cn(styles.button.primaryNeon, "w-[110px]")}>
|
||||||
|
<span className={styles.text.neonBlue}>Complete</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -43,10 +43,10 @@ export const buttonStyles = {
|
|||||||
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90 px-4 py-2 rounded-lg font-medium transition-all duration-200 shadow-sm hover:shadow-md",
|
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90 px-4 py-2 rounded-lg font-medium transition-all duration-200 shadow-sm hover:shadow-md",
|
||||||
|
|
||||||
// Icon button
|
// Icon button
|
||||||
icon: "p-2 rounded-lg hover:bg-accent transition-all duration-200",
|
icon: "p-2 rounded-lg hover:bg-accent transition-all duration-200 hover:-translate-y-0.5",
|
||||||
|
|
||||||
// Icon button with neon effect
|
// Icon button with neon effect
|
||||||
iconNeon: "p-2 rounded-lg hover:bg-accent transition-all duration-200 hover:shadow-[0_0_15px_rgba(104,147,200,0.4)]",
|
iconNeon: "p-2 rounded-lg hover:bg-accent transition-all duration-200 hover:shadow-[0_0_15px_rgba(104,147,200,0.4)] hover:-translate-y-0.5",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Layout Styles
|
// Layout Styles
|
||||||
@@ -104,13 +104,13 @@ export const textStyles = {
|
|||||||
// Badge/Chip Styles
|
// Badge/Chip Styles
|
||||||
export const badgeStyles = {
|
export const badgeStyles = {
|
||||||
// Default badge
|
// Default badge
|
||||||
default: "inline-flex items-center px-2.5 py-0.5 rounded-md text-xs font-medium bg-primary/20 text-primary border border-primary/30",
|
default: "inline-flex items-center px-2.5 py-0.5 rounded-md text-xs font-medium bg-primary/20 text-primary border border-primary/30 cursor-default select-none",
|
||||||
|
|
||||||
// Priority badges
|
// Priority badges
|
||||||
low: "inline-flex items-center px-2.5 py-0.5 rounded-md text-xs font-medium bg-green-500/20 text-green-400 border border-green-500/30",
|
low: "inline-flex items-center px-2.5 py-0.5 rounded-md text-xs font-medium bg-green-500/20 text-green-400 border border-green-500/30 cursor-default select-none",
|
||||||
medium: "inline-flex items-center px-2.5 py-0.5 rounded-md text-xs font-medium bg-yellow-500/20 text-yellow-400 border border-yellow-500/30",
|
medium: "inline-flex items-center px-2.5 py-0.5 rounded-md text-xs font-medium bg-yellow-500/20 text-yellow-400 border border-yellow-500/30 cursor-default select-none",
|
||||||
high: "inline-flex items-center px-2.5 py-0.5 rounded-md text-xs font-medium bg-orange-500/20 text-orange-400 border border-orange-500/30",
|
high: "inline-flex items-center px-2.5 py-0.5 rounded-md text-xs font-medium bg-orange-500/20 text-orange-400 border border-orange-500/30 cursor-default select-none",
|
||||||
critical: "inline-flex items-center px-2.5 py-0.5 rounded-md text-xs font-medium bg-red-500/20 text-red-400 border border-red-500/30",
|
critical: "inline-flex items-center px-2.5 py-0.5 rounded-md text-xs font-medium bg-red-500/20 text-red-400 border border-red-500/30 cursor-default select-none",
|
||||||
|
|
||||||
// Status badges
|
// Status badges
|
||||||
success: "inline-flex items-center px-2.5 py-0.5 rounded-md text-xs font-medium bg-green-500/20 text-green-400 border border-green-500/30",
|
success: "inline-flex items-center px-2.5 py-0.5 rounded-md text-xs font-medium bg-green-500/20 text-green-400 border border-green-500/30",
|
||||||
|
|||||||
Reference in New Issue
Block a user