Skip to content

Commit

Permalink
variables
Browse files Browse the repository at this point in the history
  • Loading branch information
Felix-Asante committed Apr 11, 2024
1 parent c4b48d7 commit 8eb06f1
Show file tree
Hide file tree
Showing 12 changed files with 355 additions and 2 deletions.
51 changes: 51 additions & 0 deletions src/actions/variables.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"use server";

import { apiConfig } from "@/lib/apiConfig";
import { apiHandler } from "@/lib/apiHandler";
import { CreateVariableDto } from "@/rules/validations";
import { SeverActionResponse, Variables } from "@/types";
import { getErrorMessage } from "@/utils/helpers";
import { Tags } from "@/utils/tags";
import { revalidateTag } from "next/cache";

export async function getVariables(): Promise<
SeverActionResponse<Variables[]>
> {
try {
const endpoint = apiConfig.variables.root();
const variables = await apiHandler<Variables[]>({
endpoint,
method: "GET",
next: { tags: [Tags.variables] },
});
return { results: variables };
} catch (error) {
return { error: getErrorMessage(error) };
}
}
export async function createVariables(data: CreateVariableDto) {
try {
const endpoint = apiConfig.variables.root();
await apiHandler<Variables[]>({
endpoint,
method: "POST",
body: data,
});
revalidateTag(Tags.variables);
} catch (error) {
return { error: getErrorMessage(error) };
}
}
export async function updateVariables(id: string, data: CreateVariableDto) {
try {
const endpoint = apiConfig.variables.get(id);
await apiHandler<Variables[]>({
endpoint,
method: "PATCH",
body: data,
});
revalidateTag(Tags.variables);
} catch (error) {
return { error: getErrorMessage(error) };
}
}
4 changes: 2 additions & 2 deletions src/app/(dashboard)/_home/chart/SalesChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ interface CellData {
revenue: string;
}
export default function SalesChart() {
const [selectedYear, setSelectedYear] = useState("2023");
const [selectedYear, setSelectedYear] = useState("2024");
const [selectedCell, setSelectedCell] = useState(0);

const [runGetSales, { loading, data }] = useServerAction<
Expand Down Expand Up @@ -103,7 +103,7 @@ export default function SalesChart() {
defaultSelectedKeys={[selectedYear]}
onChange={(e) => setSelectedYear(e.target.value)}
>
{getAllYearsFrom(2023).map((year) => (
{getAllYearsFrom(2024).map((year) => (
<SelectItem key={year} value={year.toString()}>
{year.toString()}
</SelectItem>
Expand Down
120 changes: 120 additions & 0 deletions src/app/(dashboard)/variables/_sections/AddVariableForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { createVariables, updateVariables } from "@/actions/variables";
import TextField from "@/components/shared/input/TextField";
import { useReactHookForm } from "@/hooks/useReactHookForm";
import { useServerAction } from "@/hooks/useServerAction";
import { CreateVariableDto, createVariableSchema } from "@/rules/validations";
import { Variables } from "@/types";
import { getErrorMessage } from "@/utils/helpers";
import {
Button,
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
} from "@nextui-org/react";
import { useEffect } from "react";
import { toast } from "sonner";

interface Props {
isOpen: boolean;
onClose: () => void;
variable: Variables | null;
}
export default function AddVariableForm(props: Props) {
const { isOpen, onClose, variable } = props;

const { control, handleSubmit, reset, setValue, clearErrors } =
useReactHookForm<CreateVariableDto>(createVariableSchema);

const [runCreateVariable, { loading }] = useServerAction<
any,
typeof createVariables
>(createVariables);
const [runUpdateVariable, { loading: updating }] = useServerAction<
any,
typeof updateVariables
>(updateVariables);

useEffect(() => {
setValue("label", variable?.label ?? "");
setValue("value", variable?.value ?? "");
}, [variable]);

const actionFunction = async (data: CreateVariableDto) =>
variable
? await runUpdateVariable(variable.id, data)
: await runCreateVariable(data);

const onSubmit = async (data: CreateVariableDto) => {
try {
const toastMessage = variable
? "Variable updated"
: "New variable created";

const response = await actionFunction(data);
if (response?.error) {
toast.error(response.error);
return;
}
toast.success(toastMessage);
reset();
onClose();
} catch (error) {
toast.error(getErrorMessage(error));
}
};

return (
<Modal size={"md"} isOpen={isOpen} onClose={onClose} isDismissable={false}>
<ModalContent>
{(onClose) => (
<>
<ModalHeader className='flex flex-col gap-1'>Variables</ModalHeader>
<ModalBody>
<TextField
control={control}
name='label'
defaultValue={variable?.label}
label='Label'
variant='bordered'
labelPlacement='outside'
radius='sm'
placeholder='Label'
/>
<TextField
control={control}
name='value'
defaultValue={variable?.value}
label='Value'
variant='bordered'
labelPlacement='outside'
radius='sm'
placeholder='Value'
/>
</ModalBody>
<ModalFooter>
<Button
radius='sm'
color='danger'
variant='light'
onPress={onClose}
// isDisabled={loading || updating}
>
Cancel
</Button>
<Button
onClick={handleSubmit(onSubmit)}
radius='sm'
color='primary'
isLoading={loading || updating}
>
Proceed
</Button>
</ModalFooter>
</>
)}
</ModalContent>
</Modal>
);
}
55 changes: 55 additions & 0 deletions src/app/(dashboard)/variables/_sections/VariableSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"use client";
import HStack from "@/components/shared/layout/HStack";
import { Variables } from "@/types";
import { pluralize } from "@/utils/helpers";
import { Button, useDisclosure } from "@nextui-org/react";
import VariablesTable from "./VariablesTable";
import { useState } from "react";
import AddVariableForm from "./AddVariableForm";

interface Props {
variables: Variables[];
}
export default function VariableSection({ variables }: Props) {
const [selectedVariable, setSelectedVariable] = useState<Variables | null>(
null,
);
const { onOpen, isOpen, onClose } = useDisclosure();
const totalVariables = variables?.length;
return (
<div>
<HStack className='items-center justify-between mb-3'>
<h3 className='font-semibold text-xl'>
{totalVariables} {pluralize("Variable", totalVariables)}
</h3>
<Button
radius='sm'
size='md'
color='primary'
disableRipple
className='font-semibold'
onClick={() => {
setSelectedVariable(null);
onOpen();
}}
>
Add Variable
</Button>
</HStack>
<VariablesTable
variables={variables}
onEdit={(variable) => {
setSelectedVariable(variable);
onOpen();
}}
/>
<AddVariableForm
isOpen={isOpen}
onClose={() => {
onClose();
}}
variable={selectedVariable}
/>
</div>
);
}
81 changes: 81 additions & 0 deletions src/app/(dashboard)/variables/_sections/VariablesTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
"use client";
import EmptyContent from "@/components/shared/EmptyContent";
import HStack from "@/components/shared/layout/HStack";
import { Variables } from "@/types";
import { pluralize } from "@/utils/helpers";
import {
Button,
Table,
TableBody,
TableCell,
TableColumn,
TableHeader,
TableRow,
useDisclosure,
} from "@nextui-org/react";
import { PencilIcon, Trash2Icon } from "lucide-react";
import React, { useState } from "react";

interface Props {
variables: Variables[];
onEdit: (variable: Variables) => void;
}
export default function VariablesTable({ variables, onEdit }: Props) {
const [selectedVariable, setSelectedVariable] = useState<Variables | null>(
null,
);
const { onOpen, isOpen, onClose } = useDisclosure();

return (
<div className='border p-3 rounded-md'>
<Table aria-label='list of variables' shadow='none' radius='none'>
<TableHeader>
<TableColumn className='w-1/2'>Name</TableColumn>
<TableColumn>Value</TableColumn>
<TableColumn>{null}</TableColumn>
</TableHeader>
<TableBody
emptyContent={
<EmptyContent
// img={illustration_empty_content}
title=''
description='No Variables added'
/>
}
>
{variables.map((variable) => (
<TableRow key={variable?.id}>
<TableCell>{variable?.label}</TableCell>
<TableCell>{variable?.value}</TableCell>
<TableCell>
<HStack className='items-center justify-end'>
<Button
radius='sm'
size='sm'
disableRipple
color='danger'
onClick={() => setSelectedVariable(variable)}
isIconOnly
disabled
>
<Trash2Icon className='text-white' size={15} />
</Button>
<Button
radius='sm'
size='sm'
disableRipple
color='success'
onClick={() => onEdit(variable)}
isIconOnly
>
<PencilIcon className='text-white' size={20} />
</Button>
</HStack>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
);
}
16 changes: 16 additions & 0 deletions src/app/(dashboard)/variables/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from "react";
import VariablesTable from "./_sections/VariablesTable";
import { getVariables } from "@/actions/variables";
import WithServerError from "@/components/hoc/WithServerError";
import VariableSection from "./_sections/VariableSection";

export default async function Variables() {
const { error, results } = await getVariables();
return (
<WithServerError error={error}>
<div className='bg-white h-screen px-5 pt-5'>
<VariableSection variables={results!} />
</div>
</WithServerError>
);
}
6 changes: 6 additions & 0 deletions src/config/constants/navigations.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
ArrowLeftRightIcon,
BadgePercentIcon,
BarChartBigIcon,
BookMarkedIcon,
Expand Down Expand Up @@ -39,4 +40,9 @@ export const SIDEBAR_NAVIGATION = [
icon: BadgePercentIcon,
href: DASHBOARD_PATHS.specials.root,
},
{
label: "Variables",
icon: ArrowLeftRightIcon,
href: DASHBOARD_PATHS.variables.root,
},
];
3 changes: 3 additions & 0 deletions src/config/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ export const DASHBOARD_PATHS = {
users: {
root: path(DASHBOARD_ROOT, "users"),
},
variables: {
root: path(DASHBOARD_ROOT, "variables"),
},
specials: {
root: path(DASHBOARD_ROOT, "specials"),
new: (query?: Query) =>
Expand Down
4 changes: 4 additions & 0 deletions src/lib/apiConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,8 @@ export const apiConfig = {
root: () => `reviews`,
get: (ratingId: string) => `reviews/${ratingId}`,
},
variables: {
root: () => `variables`,
get: (variableId: string) => `variables/${variableId}`,
},
};
8 changes: 8 additions & 0 deletions src/rules/validations/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { z } from "zod";

export const createVariableSchema = z.object({
label: z.string({ required_error: "variable name is required" }),
value: z.string({ required_error: "variable value is required" }),
});

export type CreateVariableDto = z.infer<typeof createVariableSchema>;
Loading

0 comments on commit 8eb06f1

Please sign in to comment.