This commit is contained in:
Majeemaj 2024-11-24 12:33:15 -05:00
parent 450f8ed4e1
commit 7d6b65e745
2 changed files with 100 additions and 119 deletions

View file

@ -2,13 +2,14 @@ import DeleteIcon from '@mui/icons-material/Delete'
import EditIcon from '@mui/icons-material/Edit' import EditIcon from '@mui/icons-material/Edit'
import { import {
Box, Box,
Button,
Chip,
CircularProgress, CircularProgress,
Container, Container,
IconButton, IconButton,
Table,
Typography, Typography,
} from '@mui/joy' } from '@mui/joy'
import React, { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import LabelModal from '../Modals/Inputs/LabelModal' import LabelModal from '../Modals/Inputs/LabelModal'
import { useLabels } from './LabelQueries' import { useLabels } from './LabelQueries'
@ -16,13 +17,17 @@ import { useLabels } from './LabelQueries'
import { Add } from '@mui/icons-material' import { Add } from '@mui/icons-material'
import { useQueryClient } from 'react-query' import { useQueryClient } from 'react-query'
import { DeleteLabel } from '../../utils/Fetcher' import { DeleteLabel } from '../../utils/Fetcher'
import { getTextColorFromBackgroundColor } from '../../utils/LabelColors'
const LabelView = () => { const LabelView = () => {
const { data: labels, isLabelsLoading, isError } = useLabels() const { data: labels, isLabelsLoading, isError } = useLabels()
const [userLabels, setUserLabels] = useState([labels]) const [userLabels, setUserLabels] = useState([labels])
const [modalOpen, setModalOpen] = useState(false) const [modalOpen, setModalOpen] = useState(false)
const [currentLabel, setCurrentLabel] = useState(null) // Label being edited or null for new label const [currentLabel, setCurrentLabel] = useState(null) // Label being edited or null for new label
const queryClient = useQueryClient() const queryClient = useQueryClient()
const handleAddLabel = () => { const handleAddLabel = () => {
setCurrentLabel(null) // Adding a new label setCurrentLabel(null) // Adding a new label
setModalOpen(true) setModalOpen(true)
@ -52,6 +57,7 @@ const LabelView = () => {
) )
setUserLabels(updatedLabels) setUserLabels(updatedLabels)
} }
useEffect(() => { useEffect(() => {
if (labels) { if (labels) {
setUserLabels(labels) setUserLabels(labels)
@ -81,56 +87,50 @@ const LabelView = () => {
return ( return (
<Container maxWidth='md'> <Container maxWidth='md'>
<Table aria-label='Manage Labels' stickyHeader hoverRow> <Typography level='h2' sx={{ my: 2 }}>
<thead> Labels
<tr> </Typography>
<th style={{ textAlign: 'center' }}>Label</th> <div className='flex flex-col gap-2'>
<th style={{ textAlign: 'center' }}>Color</th> {userLabels.map(label => (
<th style={{ textAlign: 'center' }}>Actions</th> <div
</tr> key={label}
</thead> className='grid w-full grid-cols-[1fr,auto,auto] rounded-lg border border-zinc-200/80 p-4 shadow-sm dark:bg-zinc-900'
<tbody> >
{userLabels.map(label => ( <Chip
<tr key={label.id}> variant='outlined'
<td>{label.name}</td> color='primary'
<td size='lg'
style={{ sx={{
// center without display flex: background: label.color,
textAlign: 'center', borderColor: label.color,
alignItems: 'center', color: getTextColorFromBackgroundColor(label.color),
justifyContent: 'center', }}
}} >
{label.name}
</Chip>
<div className='flex gap-2'>
<Button
size='sm'
variant='soft'
color='neutral'
onClick={() => handleEditLabel(label)}
startDecorator={<EditIcon />}
> >
<Box Edit
width={20} </Button>
height={20} <IconButton
borderRadius='50%' size='sm'
sx={{ variant='soft'
backgroundColor: label.color, onClick={() => handleDeleteLabel(label.id)}
}} color='danger'
/>
</td>
<td
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
> >
<IconButton onClick={() => handleEditLabel(label)}> <DeleteIcon />
<EditIcon /> </IconButton>
</IconButton> </div>
<IconButton </div>
onClick={() => handleDeleteLabel(label.id)} ))}
color='danger' </div>
>
<DeleteIcon />
</IconButton>
</td>
</tr>
))}
</tbody>
</Table>
{userLabels.length === 0 && ( {userLabels.length === 0 && (
<Typography textAlign='center' mt={2}> <Typography textAlign='center' mt={2}>
@ -146,6 +146,7 @@ const LabelView = () => {
label={currentLabel} label={currentLabel}
/> />
)} )}
<Box <Box
sx={{ sx={{
position: 'fixed', position: 'fixed',

View file

@ -1,3 +1,4 @@
import React, { useEffect, useState } from 'react'
import { import {
Box, Box,
Button, Button,
@ -9,18 +10,16 @@ import {
Select, Select,
Typography, Typography,
} from '@mui/joy' } from '@mui/joy'
import { useQueryClient, useMutation } from 'react-query'
import React, { useEffect } from 'react'
import { useQueryClient } from 'react-query'
import { CreateLabel, UpdateLabel } from '../../../utils/Fetcher' import { CreateLabel, UpdateLabel } from '../../../utils/Fetcher'
import LABEL_COLORS from '../../../utils/LabelColors' import LABEL_COLORS from '../../../utils/LabelColors'
import { useLabels } from '../../Labels/LabelQueries' import { useLabels } from '../../Labels/LabelQueries'
function LabelModal({ isOpen, onClose, onSave, label }) { function LabelModal({ isOpen, onClose, label }) {
const [labelName, setLabelName] = React.useState('') const [labelName, setLabelName] = useState('')
const [color, setColor] = React.useState('') const [color, setColor] = useState('')
const [error, setError] = React.useState('') const [error, setError] = useState('')
const { data: userLabels, isLoadingLabels } = useLabels() const { data: userLabels = [] } = useLabels()
const queryClient = useQueryClient() const queryClient = useQueryClient()
// Populate the form fields when editing // Populate the form fields when editing
@ -35,48 +34,48 @@ function LabelModal({ isOpen, onClose, onSave, label }) {
setError('') setError('')
}, [label]) }, [label])
// Validation logic
const validateLabel = () => { const validateLabel = () => {
if (!labelName || labelName.trim() === '') { if (!labelName.trim()) {
setError('Name cannot be empty') setError('Name cannot be empty')
return false return false
} else if ( }
if (
userLabels.some( userLabels.some(
userLabel => userLabel.name === labelName && userLabel.id !== label.id, userLabel => userLabel.name === labelName && userLabel.id !== label?.id,
) )
) { ) {
setError('Label with this name already exists') setError('Label with this name already exists')
return false return false
} else if (color === '') { }
if (!color) {
setError('Please select a color') setError('Please select a color')
return false return false
} }
return true return true
} }
const handleSave = () => { // Mutation for saving labels
if (!validateLabel()) { const saveLabelMutation = useMutation(
return newLabel =>
} label
? UpdateLabel({ id: label.id, ...newLabel })
const saveAction = label : CreateLabel(newLabel),
? UpdateLabel({ id: label.id, name: labelName, color }) {
: CreateLabel({ name: labelName, color }) onSuccess: () => {
queryClient.invalidateQueries('labels')
saveAction.then(res => {
if (res.error) {
console.log(res.error)
setError('Failed to save label. Please try again.')
return
}
res.json().then(data => {
if (data.error) {
setError('Failed to save label. Please try again.')
return
}
onSave({ id: data?.res?.id, name: labelName, color })
onClose() onClose()
}) },
}) onError: () => {
setError('Failed to save label. Please try again.')
},
},
)
const handleSave = () => {
if (!validateLabel()) return
saveLabelMutation.mutate({ name: labelName, color })
} }
return ( return (
@ -85,51 +84,39 @@ function LabelModal({ isOpen, onClose, onSave, label }) {
<Typography level='title-md' mb={1}> <Typography level='title-md' mb={1}>
{label ? 'Edit Label' : 'Add Label'} {label ? 'Edit Label' : 'Add Label'}
</Typography> </Typography>
<FormControl> <FormControl>
<Typography level='body-sm' alignSelf={'start'}> <Typography gutterBottom level='body-sm' alignSelf='start'>
Name Name
</Typography> </Typography>
<Input <Input
margin='normal'
required
fullWidth fullWidth
name='labelName'
type='text'
id='labelName' id='labelName'
value={labelName} value={labelName}
onChange={e => setLabelName(e.target.value)} onChange={e => setLabelName(e.target.value)}
/> />
</FormControl> </FormControl>
{/* Color Selection */}
<FormControl> <FormControl>
<Typography level='body-sm' alignSelf={'start'}> <Typography gutterBottom level='body-sm' alignSelf='start'>
Color: Color
</Typography> </Typography>
<Select <Select
label='Color'
value={color} value={color}
onChange={(e, value) => value && setColor(value)}
renderValue={selected => ( renderValue={selected => (
<Typography <Typography
key={selected.value}
startDecorator={ startDecorator={
<Box <Box
className='h-4 w-4' className='h-4 w-4'
borderRadius={10} borderRadius={10}
sx={{ sx={{ background: selected.value }}
background: selected.value,
shadow: { xs: 1 },
}}
/> />
} }
> >
{selected.label} {selected.label}
</Typography> </Typography>
)} )}
onChange={(e, value) => {
value && setColor(value)
}}
> >
{LABEL_COLORS.map(val => ( {LABEL_COLORS.map(val => (
<Option key={val.value} value={val.value}> <Option key={val.value} value={val.value}>
@ -138,31 +125,24 @@ function LabelModal({ isOpen, onClose, onSave, label }) {
width={20} width={20}
height={20} height={20}
borderRadius={10} borderRadius={10}
sx={{ sx={{ background: val.value }}
background: val.value,
}}
/> />
<Typography <Typography sx={{ ml: 1 }} variant='caption'>
sx={{
ml: 1,
color: 'text.secondary',
}}
variant='caption'
>
{val.name} {val.name}
</Typography> </Typography>
</Box> </Box>
</Option> </Option>
))} ))}
</Select> </Select>
{error && (
<Typography color='warning' level='body-sm'>
{error}
</Typography>
)}
</FormControl> </FormControl>
<Box display={'flex'} justifyContent={'space-around'} mt={1}> {error && (
<Typography color='warning' level='body-sm'>
{error}
</Typography>
)}
<Box display='flex' justifyContent='space-around' mt={1}>
<Button onClick={handleSave} fullWidth sx={{ mr: 1 }}> <Button onClick={handleSave} fullWidth sx={{ mr: 1 }}>
{label ? 'Save Changes' : 'Add Label'} {label ? 'Save Changes' : 'Add Label'}
</Button> </Button>