Update ChoreCard: Multi-user display, birthday icons, points, and admin edit button
- Display multiple assigned users with completion status per user - Show birthday emoji 🎂 for users with birthday today - Display points with ⭐ emoji - Add Edit button for admins (calls onEdit prop) - Show "Birthday chore! Give them a break today" message - Update frequency icons (added fortnightly, on_trigger) - Check if current user is assigned and show appropriate completion button - Only show Complete button if user is assigned and hasn't completed yet
This commit is contained in:
@@ -1,13 +1,16 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Chore } from '../api/chores';
|
import { Chore } from '../api/chores';
|
||||||
|
import { useAuth } from '../contexts/AuthContext';
|
||||||
|
|
||||||
interface ChoreCardProps {
|
interface ChoreCardProps {
|
||||||
chore: Chore;
|
chore: Chore;
|
||||||
onComplete: (id: number) => void;
|
onComplete: (id: number) => void;
|
||||||
onDelete: (id: number) => void;
|
onDelete: (id: number) => void;
|
||||||
|
onEdit?: (id: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChoreCard: React.FC<ChoreCardProps> = ({ chore, onComplete, onDelete }) => {
|
const ChoreCard: React.FC<ChoreCardProps> = ({ chore, onComplete, onDelete, onEdit }) => {
|
||||||
|
const { user } = useAuth();
|
||||||
const statusColors = {
|
const statusColors = {
|
||||||
pending: 'bg-yellow-100 text-yellow-800',
|
pending: 'bg-yellow-100 text-yellow-800',
|
||||||
in_progress: 'bg-blue-100 text-blue-800',
|
in_progress: 'bg-blue-100 text-blue-800',
|
||||||
@@ -18,14 +21,35 @@ const ChoreCard: React.FC<ChoreCardProps> = ({ chore, onComplete, onDelete }) =>
|
|||||||
const frequencyIcons = {
|
const frequencyIcons = {
|
||||||
daily: '📅',
|
daily: '📅',
|
||||||
weekly: '📆',
|
weekly: '📆',
|
||||||
monthly: '🗓️',
|
fortnightly: '🗓️',
|
||||||
once: '⏱️',
|
monthly: '📊',
|
||||||
|
on_trigger: '⏱️',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Check if current user is assigned to this chore
|
||||||
|
const isAssignedToMe = chore.assigned_users?.some(u => u.id === user?.id);
|
||||||
|
const myAssignment = chore.assigned_users?.find(u => u.id === user?.id);
|
||||||
|
const myCompletionStatus = myAssignment?.completed_at ? 'completed' : chore.status;
|
||||||
|
|
||||||
|
// Check if today is anyone's birthday
|
||||||
|
const today = new Date();
|
||||||
|
const hasBirthdayUser = chore.assigned_users?.some(u => {
|
||||||
|
if (!u.birthday) return false;
|
||||||
|
const bday = new Date(u.birthday);
|
||||||
|
return bday.getMonth() === today.getMonth() && bday.getDate() === today.getDate();
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white rounded-lg shadow-md p-4 hover:shadow-lg transition-shadow">
|
<div className="bg-white rounded-lg shadow-md p-4 hover:shadow-lg transition-shadow">
|
||||||
<div className="flex justify-between items-start mb-3">
|
<div className="flex justify-between items-start mb-3">
|
||||||
|
<div className="flex-1">
|
||||||
<h3 className="text-lg font-semibold text-gray-900">{chore.title}</h3>
|
<h3 className="text-lg font-semibold text-gray-900">{chore.title}</h3>
|
||||||
|
{chore.points > 0 && (
|
||||||
|
<div className="flex items-center mt-1">
|
||||||
|
<span className="text-sm font-medium text-amber-600">⭐ {chore.points} pts</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<span className={`px-2 py-1 rounded-full text-xs font-medium ${statusColors[chore.status]}`}>
|
<span className={`px-2 py-1 rounded-full text-xs font-medium ${statusColors[chore.status]}`}>
|
||||||
{chore.status.replace('_', ' ')}
|
{chore.status.replace('_', ' ')}
|
||||||
</span>
|
</span>
|
||||||
@@ -44,16 +68,39 @@ const ChoreCard: React.FC<ChoreCardProps> = ({ chore, onComplete, onDelete }) =>
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center text-sm text-gray-500">
|
<div className="flex items-center text-sm text-gray-500">
|
||||||
<span className="mr-2">{frequencyIcons[chore.frequency]}</span>
|
<span className="mr-2">{frequencyIcons[chore.frequency] || '📋'}</span>
|
||||||
<span className="capitalize">{chore.frequency}</span>
|
<span className="capitalize">{chore.frequency.replace('_', ' ')}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{chore.assigned_user && (
|
{/* Assigned Users */}
|
||||||
<div className="flex items-center text-sm text-gray-500">
|
{chore.assigned_users && chore.assigned_users.length > 0 && (
|
||||||
|
<div className="space-y-1">
|
||||||
|
<div className="flex items-center text-sm font-medium text-gray-700">
|
||||||
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
|
||||||
</svg>
|
</svg>
|
||||||
<span>{chore.assigned_user.full_name}</span>
|
Assigned to:
|
||||||
|
</div>
|
||||||
|
<div className="pl-6 space-y-1">
|
||||||
|
{chore.assigned_users.map(assignedUser => {
|
||||||
|
const isBirthday = assignedUser.birthday && (() => {
|
||||||
|
const bday = new Date(assignedUser.birthday);
|
||||||
|
return bday.getMonth() === today.getMonth() && bday.getDate() === today.getDate();
|
||||||
|
})();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={assignedUser.id} className="flex items-center justify-between text-sm">
|
||||||
|
<span className={`${assignedUser.id === user?.id ? 'font-medium text-blue-600' : 'text-gray-600'}`}>
|
||||||
|
{assignedUser.full_name}
|
||||||
|
{isBirthday && ' 🎂'}
|
||||||
|
</span>
|
||||||
|
{assignedUser.completed_at && (
|
||||||
|
<span className="text-xs text-green-600">✓ Done</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -68,21 +115,50 @@ const ChoreCard: React.FC<ChoreCardProps> = ({ chore, onComplete, onDelete }) =>
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
{chore.status !== 'completed' && (
|
{isAssignedToMe && myCompletionStatus !== 'completed' && (
|
||||||
<button
|
<button
|
||||||
onClick={() => onComplete(chore.id)}
|
onClick={() => onComplete(chore.id)}
|
||||||
className="flex-1 px-3 py-2 bg-green-600 text-white text-sm rounded-lg hover:bg-green-700 transition-colors"
|
className="flex-1 px-3 py-2 bg-green-600 text-white text-sm rounded-lg hover:bg-green-700 transition-colors flex items-center justify-center gap-1"
|
||||||
>
|
>
|
||||||
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||||
|
</svg>
|
||||||
Complete
|
Complete
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{user?.is_admin && onEdit && (
|
||||||
|
<button
|
||||||
|
onClick={() => onEdit(chore.id)}
|
||||||
|
className="px-3 py-2 bg-blue-100 text-blue-700 text-sm rounded-lg hover:bg-blue-200 transition-colors flex items-center gap-1"
|
||||||
|
>
|
||||||
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{user?.is_admin && (
|
||||||
<button
|
<button
|
||||||
onClick={() => onDelete(chore.id)}
|
onClick={() => onDelete(chore.id)}
|
||||||
className="px-3 py-2 bg-red-100 text-red-700 text-sm rounded-lg hover:bg-red-200 transition-colors"
|
className="px-3 py-2 bg-red-100 text-red-700 text-sm rounded-lg hover:bg-red-200 transition-colors"
|
||||||
>
|
>
|
||||||
Delete
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||||
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{hasBirthdayUser && (
|
||||||
|
<div className="mt-3 pt-3 border-t border-gray-200">
|
||||||
|
<p className="text-xs text-purple-600 flex items-center gap-1">
|
||||||
|
<span>🎂</span>
|
||||||
|
<span>Birthday chore! Give them a break today.</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user