Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Checklist #2168

Merged
merged 9 commits into from
May 27, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Checklist
  • Loading branch information
timmo001 committed May 27, 2023
commit dbada1610da06029dbe4bc5883a670fe9e271455
16 changes: 16 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,27 @@ model Widget {
homeAssistant WidgetHomeAssistant[]
image WidgetImage[]
markdown WidgetMarkdown[]
checklist WidgetChecklist[]

@@index([id, sectionId], name: "id_sectionId_unique")
@@index([sectionId, position], name: "sectionId_position_unique")
}

model WidgetChecklist {
widgetId String @id
widget Widget @relation(fields: [widgetId], references: [id], onDelete: Cascade)
items WidgetChecklistItem[]
}

model WidgetChecklistItem {
id String @id @default(cuid())
position Int @default(10)
content String
checked Boolean @default(false)
checklist WidgetChecklist @relation(fields: [checklistWidgetId], references: [widgetId])
checklistWidgetId String
}

model WidgetFrame {
widgetId String @id
widget Widget @relation(fields: [widgetId], references: [id], onDelete: Cascade)
Expand Down
19 changes: 18 additions & 1 deletion src/components/dashboard/views/Widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
widgetUpdate,
} from "@/utils/serverActions/widget";
import { WidgetAction, WidgetType } from "@/types/widget.type";
import { WidgetChecklist } from "@/components/dashboard/views/widgets/Checklist";
import { WidgetFrame } from "@/components/dashboard/views/widgets/Frame";
import { WidgetHomeAssistant } from "@/components/dashboard/views/widgets/HomeAssistant";
import { WidgetImage } from "@/components/dashboard/views/widgets/Image";
Expand Down Expand Up @@ -76,6 +77,14 @@ export function Widget({
const widgetView: JSX.Element = useMemo(() => {
if (!widgetData) return <Skeleton variant="text" />;
switch (data.type) {
case WidgetType.Checklist:
return (
<WidgetChecklist
dashboardId={dashboardId}
data={widgetData}
sectionId={data.sectionId}
/>
);
case WidgetType.Frame:
return <WidgetFrame data={widgetData} />;
case WidgetType.HomeAssistant:
Expand All @@ -100,7 +109,15 @@ export function Widget({
default:
return <div>Unknown widget type</div>;
}
}, [data.type, editing, expanded, handleInteraction, widgetData]);
}, [
dashboardId,
data.sectionId,
data.type,
editing,
expanded,
handleInteraction,
widgetData,
]);

return (
<WidgetBase
Expand Down
142 changes: 142 additions & 0 deletions src/components/dashboard/views/widgets/Checklist.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
"use client";
import type { WidgetChecklistItem as WidgetChecklistItemModel } from "@prisma/client";
import {
Button,
Unstable_Grid2 as Grid2,
IconButton,
InputAdornment,
TextField,
} from "@mui/material";
import {
DeleteOutlineRounded,
CheckBoxOutlineBlankRounded,
CheckBoxRounded,
AddRounded,
} from "@mui/icons-material";
import { useState } from "react";

import { WidgetChecklistModel } from "@/types/widget.type";
import { widgetChecklistUpdate } from "@/utils/serverActions/widget";

function WidgetChecklistItem({
dashboardId,
data,
sectionId,
}: {
dashboardId: string;
data: WidgetChecklistItemModel;
sectionId: string;
}): JSX.Element {
const { id, checklistWidgetId, content } = data;
const [checked, setChecked] = useState<boolean>(data.checked);
return (
<Grid2>
<TextField
defaultValue={content}
fullWidth
margin="dense"
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<IconButton
aria-label="Check item"
onClick={() => {
setChecked(!checked);
widgetChecklistUpdate(
dashboardId,
sectionId,
checklistWidgetId,
id,
"checked",
!checked
);
}}
edge="start"
>
{checked ? (
<CheckBoxRounded />
) : (
<CheckBoxOutlineBlankRounded />
)}
</IconButton>
</InputAdornment>
),
endAdornment: (
<InputAdornment position="end">
<IconButton
aria-label="Delete item"
onClick={() => {
widgetChecklistUpdate(
dashboardId,
sectionId,
checklistWidgetId,
id,
"id",
"DELETE"
);
}}
edge="end"
>
<DeleteOutlineRounded />
</IconButton>
</InputAdornment>
),
}}
onChange={async (e) => {
await widgetChecklistUpdate(
dashboardId,
sectionId,
checklistWidgetId,
id,
"content",
e.target.value
);
}}
/>
</Grid2>
);
}

export function WidgetChecklist({
dashboardId,
data,
sectionId,
}: {
dashboardId: string;
data: WidgetChecklistModel;
sectionId: string;
}): JSX.Element {
return (
<Grid2 container direction="column">
{data.items.map((item: WidgetChecklistItemModel) => (
<WidgetChecklistItem
key={item.id}
dashboardId={dashboardId}
data={item}
sectionId={sectionId}
/>
))}
<Grid2>
<Button
fullWidth
variant="outlined"
sx={{ mt: 1 }}
onClick={async () => {
await widgetChecklistUpdate(
dashboardId,
sectionId,
data.widgetId,
"",
"content",
""
);
}}
>
<AddRounded />
Add item
</Button>
</Grid2>
</Grid2>
);
}
11 changes: 10 additions & 1 deletion src/types/widget.type.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import type { Section, Widget } from "@prisma/client";
import type {
Section,
Widget,
WidgetChecklist,
WidgetChecklistItem,
} from "@prisma/client";

// Combined types for widget with section
export type WidgetWithSectionModel = Widget & {
Expand All @@ -22,3 +27,7 @@ export enum WidgetType {
News = "news",
RSS = "rss",
}

export type WidgetChecklistModel = WidgetChecklist & {
items: Array<WidgetChecklistItem>;
};
87 changes: 87 additions & 0 deletions src/utils/serverActions/widget.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use server";
import type {
Widget as WidgetModel,
WidgetChecklistItem as WidgetChecklistItemModel,
WidgetFrame as WidgetFrameModel,
WidgetHomeAssistant as WidgetHomeAssistantModel,
WidgetImage as WidgetImageModel,
Expand Down Expand Up @@ -68,6 +69,23 @@ export async function widgetGetData(
console.log("Get widget data:", { widgetId, type });
let data;
switch (type) {
case WidgetType.Checklist:
data = await prisma.widgetChecklist.findUnique({
include: { items: { orderBy: { position: "asc" } } },
where: { widgetId },
});
if (data) return data;
return await prisma.widgetChecklist.create({
data: {
items: {
create: {
content: "",
},
},
widget: { connect: { id: widgetId } },
},
include: { items: true },
});
case WidgetType.Frame:
data = await prisma.widgetFrame.findUnique({
where: { widgetId },
Expand Down Expand Up @@ -180,6 +198,75 @@ export async function widgetUpdate(
return newData;
}

export async function widgetChecklistUpdate(
dashboardId: string,
sectionId: string,
widgetId: string,
checklistItemId: string,
name: keyof WidgetChecklistItemModel,
value: WidgetChecklistItemModel[keyof WidgetChecklistItemModel]
): Promise<WidgetChecklistItemModel> {
console.log("Update widget checklist:", {
dashboardId,
sectionId,
widgetId,
checklistItemId,
name,
value,
});

let newData: WidgetChecklistItemModel;
if (name === "id" && value === "DELETE") {
newData = await prisma.widgetChecklistItem.delete({
where: {
id: checklistItemId,
},
});
} else {
newData = await prisma.widgetChecklistItem.upsert({
create: {
content: "",
position: 9999,
[name]: value,
checklist: {
connect: {
widgetId,
},
},
},
update: {
[name]: value,
},
where: {
id: checklistItemId,
},
});
}

// Sort checklist items
const items = await prisma.widgetChecklistItem.findMany({
select: { id: true },
where: { checklist: { widgetId: widgetId } },
});
for (let i = 0; i < items.length; i++) {
const item = items[i];
await prisma.widgetChecklistItem.update({
data: { position: i * 10 },
where: { id: item.id },
});
}

// Load new data with new position
newData =
(await prisma.widgetChecklistItem.findUnique({
where: { id: newData.id },
})) || newData;

await widgetRevalidate(dashboardId, sectionId, widgetId);

return newData;
}

export async function widgetFrameUpdate(
dashboardId: string,
sectionId: string,
Expand Down