Skip to content

Commit f094ae6

Browse files
committed
Label text color
1 parent 9fe7d51 commit f094ae6

26 files changed

+263
-153
lines changed

api/labels/useCreateLabel.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ export const useCreateLabel = () => {
88
const queryClient = useQueryClient();
99

1010
const { mutateAsync: createLabel, isPending } = useMutation({
11-
mutationFn: async (newLabel: Pick<Label, "name" | "color" | "type">) => {
11+
mutationFn: async (
12+
newLabel: Pick<Label, "name" | "color" | "type" | "text_color">
13+
) => {
1214
const response: ApiResponse<Label> = await fetchService(
1315
LABELS_API_ENDPOINT,
1416
{

app/api/categories/[id]/route.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,12 @@ export async function PUT(
3232
.object({
3333
name: z.string().min(1),
3434
labels: z.array(
35-
z.object({ id: z.string(), name: z.string(), color: z.string() })
35+
z.object({
36+
id: z.string(),
37+
name: z.string(),
38+
color: z.string(),
39+
text_color: z.string(),
40+
})
3641
),
3742
})
3843
.parse(body);

app/api/categories/route.ts

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export async function GET(req: NextRequest) {
5050
id: { $toString: "$$label._id" },
5151
name: "$$label.name",
5252
color: "$$label.color",
53+
text_color: "$$label.text_color",
5354
},
5455
},
5556
},

app/api/labels/[id]/route.ts

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const labelSchema = z.object({
1616
color: z.string().min(1),
1717
category_ids: z.array(z.string()),
1818
type: z.string().min(1),
19+
text_color: z.string().min(1),
1920
});
2021

2122
type LabelSchema = z.infer<typeof labelSchema>;

app/api/labels/route.ts

+3
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export async function GET(req: NextRequest) {
5353
color: 1,
5454
category_ids: 1,
5555
type: 1,
56+
text_color: 1,
5657
},
5758
},
5859
]);
@@ -79,6 +80,7 @@ export async function POST(req: NextRequest) {
7980
name: z.string().min(1),
8081
color: z.string(),
8182
type: z.string().min(1),
83+
text_color: z.string().min(1),
8284
})
8385
.parse(body);
8486

@@ -122,6 +124,7 @@ export async function POST(req: NextRequest) {
122124
id: label._id.toString(),
123125
name: label.name,
124126
color: label.color,
127+
text_color: label.text_color,
125128
},
126129
},
127130
});

components/categories/CategoryCard.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ export const CategoryCard = ({
153153
key={label.id}
154154
labelName={label.name}
155155
labelColor={label.color}
156+
textColor={label.text_color}
156157
/>
157158
))}
158159
</Grid>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import * as z from "zod";
2+
import { FieldValues } from "react-hook-form";
3+
import { CATEGORY_TYPE, Label } from "@/types";
4+
import { useUpdateLabel, useCreateLabel, useGetCategories } from "@/api";
5+
import { EditLabelModal } from "../labels";
6+
7+
interface LabelFormModalProps {
8+
isOpen: boolean;
9+
onClose: () => void;
10+
label?: { id: string; name: string; color: string };
11+
editedLabel: Label | null;
12+
}
13+
14+
const schema = z.object({
15+
name: z.string().min(1, { message: "Required" }),
16+
color: z.string(),
17+
text_color: z.string(),
18+
});
19+
20+
export const EditCategoryLabelModal = ({
21+
isOpen,
22+
onClose,
23+
label,
24+
editedLabel,
25+
}: LabelFormModalProps) => {
26+
const { createLabel, isPending } = useCreateLabel();
27+
const { updateLabel, isPending: isLoadingUpdate } = useUpdateLabel();
28+
const { refetch } = useGetCategories();
29+
30+
const onSaveLabel = async (data: FieldValues) => {
31+
try {
32+
if (editedLabel) {
33+
await updateLabel({
34+
...editedLabel,
35+
name: data.name,
36+
color: data.color,
37+
text_color: data.text_color,
38+
});
39+
40+
return;
41+
}
42+
43+
await createLabel({
44+
name: data.name,
45+
color: data.color,
46+
type: CATEGORY_TYPE,
47+
text_color: data.text_color,
48+
});
49+
} finally {
50+
onClose();
51+
refetch();
52+
}
53+
};
54+
55+
return (
56+
<EditLabelModal
57+
onSaveLabel={onSaveLabel}
58+
editedLabel={editedLabel}
59+
isLoadingUpdate={isLoadingUpdate}
60+
isOpen={isOpen}
61+
isPending={isPending}
62+
onClose={onClose}
63+
schema={schema}
64+
label={label}
65+
/>
66+
);
67+
};

components/categories/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ export * from "./CreateCategoryButton";
33
export * from "./CategoryCard";
44
export * from "./DeleteCategoryButton";
55
export * from "./CategoryCardSkeleton";
6+
export * from "./EditCategoryLabelModal";

components/code-items/CodeItem.tsx

+6-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { grey } from "@mui/material/colors";
1414
import { Edit, Delete } from "@mui/icons-material";
1515
import { useProgress } from "@/providers/ProgressBarProvider";
1616
import { DeleteCodeItemButton } from "./DeleteCodeItemButton";
17+
import { getTextColor } from "@/helpers/getTextColor";
1718

1819
interface CodeItemProps {
1920
codeItemLabels: Label[];
@@ -176,7 +177,11 @@ export const CodeItem = ({
176177
bgcolor={label.color}
177178
key={label.id}
178179
>
179-
<Typography>{label.name}</Typography>
180+
<Typography
181+
color={getTextColor({ textColor: label.text_color })}
182+
>
183+
{label.name}
184+
</Typography>
180185
</Box>
181186
))}
182187
</Box>

components/code-items/CodeItemLabelsModal.tsx

+6-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import EditIcon from "@mui/icons-material/Edit";
77
import CloseIcon from "@mui/icons-material/Close";
88
import DeleteIcon from "@mui/icons-material/Delete";
99
import { Modal, Box, Typography, IconButton, Grid } from "@mui/material";
10+
import { getTextColor } from "@/helpers/getTextColor";
1011

1112
interface CodeItemLabelsModalProps {
1213
isOpen: boolean;
@@ -93,7 +94,11 @@ export const CodeItemLabelsModal = ({
9394
minWidth: 230,
9495
}}
9596
>
96-
<Typography color="text.primary">{label.name}</Typography>
97+
<Typography
98+
color={getTextColor({ textColor: label.text_color })}
99+
>
100+
{label.name}
101+
</Typography>
97102
</Grid>
98103

99104
<IconButton

components/code-items/EditCodeItemLabelModal.tsx

+13-83
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,11 @@
22

33
import * as z from "zod";
44
import { FieldValues } from "react-hook-form";
5-
import { Modal, Box, Typography, IconButton } from "@mui/material";
6-
import { grey } from "@mui/material/colors";
7-
import CloseIcon from "@mui/icons-material/Close";
85
import { CODE_ITEM_TYPE, CODEITEMS_API_ENDPOINT, Label } from "@/types";
96
import { useUpdateLabel, useCreateLabel, fetchOneCodeItem } from "@/api";
10-
import { Form } from "../Form";
11-
import { Button } from "../buttons";
12-
import { TextInput } from "../inputs";
137
import { useQuery, useQueryClient } from "@tanstack/react-query";
148
import { useParams } from "next/navigation";
9+
import { EditLabelModal } from "../labels";
1510

1611
interface EditCodeItemLabelModalProps {
1712
isOpen: boolean;
@@ -23,6 +18,7 @@ interface EditCodeItemLabelModalProps {
2318
const schema = z.object({
2419
name: z.string().min(1, { message: "Required" }),
2520
color: z.string(),
21+
text_color: z.string(),
2622
});
2723

2824
export const EditCodeItemLabelModal = ({
@@ -48,6 +44,7 @@ export const EditCodeItemLabelModal = ({
4844
...editedLabel,
4945
name: data.name,
5046
color: data.color,
47+
text_color: data.text_color,
5148
});
5249

5350
await queryClient.invalidateQueries({
@@ -61,6 +58,7 @@ export const EditCodeItemLabelModal = ({
6158
name: data.name,
6259
color: data.color,
6360
type: CODE_ITEM_TYPE,
61+
text_color: data.text_color,
6462
});
6563

6664
await queryClient.invalidateQueries({
@@ -73,83 +71,15 @@ export const EditCodeItemLabelModal = ({
7371
};
7472

7573
return (
76-
<Modal
77-
open={isOpen}
74+
<EditLabelModal
75+
onSaveLabel={onSaveLabel}
76+
editedLabel={editedLabel}
77+
isLoadingUpdate={isLoadingUpdate}
78+
isOpen={isOpen}
79+
isPending={isPending}
7880
onClose={onClose}
79-
slotProps={{
80-
backdrop: {
81-
invisible: true,
82-
},
83-
}}
84-
>
85-
<Box
86-
sx={{
87-
position: "absolute",
88-
top: "50%",
89-
left: "50%",
90-
transform: "translate(-50%, -50%)",
91-
minWidth: 300,
92-
bgcolor: "background.paper",
93-
boxShadow: 24,
94-
p: 4,
95-
borderRadius: 2,
96-
}}
97-
>
98-
<Typography variant="h6" component="h2" sx={{ mb: 2 }}>
99-
{editedLabel ? "Edit label" : "Create label"}
100-
</Typography>
101-
102-
<IconButton
103-
aria-label="close"
104-
onClick={onClose}
105-
sx={{
106-
position: "absolute",
107-
right: 8,
108-
top: 8,
109-
}}
110-
>
111-
<CloseIcon />
112-
</IconButton>
113-
114-
<Form schema={schema} onSubmit={onSaveLabel} sx={{ height: 1 }}>
115-
<TextInput
116-
fullWidth
117-
label="Label Name"
118-
name="name"
119-
defaultValue={editedLabel?.name}
120-
required
121-
/>
122-
123-
<Typography sx={{ mt: 2, mb: 1 }}>Label Color</Typography>
124-
125-
<Box>
126-
<TextInput
127-
name="color"
128-
type="color"
129-
fullWidth
130-
variant="standard"
131-
defaultValue={editedLabel?.color || label?.color || grey[100]}
132-
inputProps={{
133-
sx: {
134-
height: "53.15px",
135-
p: 0,
136-
cursor: "pointer",
137-
borderRadius: 2,
138-
},
139-
}}
140-
/>
141-
</Box>
142-
143-
<Button
144-
variant="contained"
145-
type="submit"
146-
sx={{ mt: 3 }}
147-
isLoading={isPending || isLoadingUpdate}
148-
>
149-
Save
150-
</Button>
151-
</Form>
152-
</Box>
153-
</Modal>
81+
schema={schema}
82+
label={label}
83+
/>
15484
);
15585
};

components/inputs/RadioGroupInput.tsx

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import {
2+
Box,
3+
FormLabel,
4+
RadioGroup,
5+
FormControlLabel,
6+
Radio,
7+
RadioGroupProps,
8+
SxProps,
9+
} from "@mui/material";
10+
import { useController } from "react-hook-form";
11+
12+
type RadioGroupInputProps = RadioGroupProps & {
13+
name: string;
14+
defaultValue?: string;
15+
choices: { value: string; label: string }[];
16+
label?: string;
17+
sx?: SxProps;
18+
};
19+
20+
export const RadioGroupInput = ({
21+
name,
22+
defaultValue,
23+
choices,
24+
label,
25+
sx,
26+
...rest
27+
}: RadioGroupInputProps) => {
28+
const { field } = useController({
29+
name,
30+
defaultValue,
31+
});
32+
33+
return (
34+
<Box sx={sx}>
35+
{label && <FormLabel id={name}>{label}</FormLabel>}
36+
37+
<RadioGroup
38+
aria-labelledby={name}
39+
defaultValue={defaultValue}
40+
name={name}
41+
value={field.value}
42+
onChange={field.onChange}
43+
{...rest}
44+
>
45+
{choices.map((choice) => {
46+
return (
47+
<FormControlLabel
48+
value={choice.value}
49+
label={choice.label}
50+
key={choice.value}
51+
control={<Radio />}
52+
/>
53+
);
54+
})}
55+
</RadioGroup>
56+
</Box>
57+
);
58+
};

components/inputs/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from "./TextInput";
22
export * from "./AutocompleteInput";
33
export * from "./SearchInput";
4+
export * from "./RadioGroupInput";

0 commit comments

Comments
 (0)