Implement local browser caching for shared groups and UI improvements
All checks were successful
Automated Container Build / build-and-push (push) Successful in 1m10s
All checks were successful
Automated Container Build / build-and-push (push) Successful in 1m10s
This commit is contained in:
parent
42ed0d275b
commit
7af0935657
37 changed files with 6141 additions and 411 deletions
BIN
dev.db
BIN
dev.db
Binary file not shown.
1
out.css
Normal file
1
out.css
Normal file
|
|
@ -0,0 +1 @@
|
|||
:root { --color-primary: #fff; }
|
||||
57
package-lock.json
generated
57
package-lock.json
generated
|
|
@ -8,6 +8,9 @@
|
|||
"name": "study-app",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@prisma/adapter-better-sqlite3": "^7.8.0",
|
||||
"@prisma/client": "^7.8.0",
|
||||
"argon2": "^0.44.0",
|
||||
|
|
@ -287,6 +290,60 @@
|
|||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dnd-kit/accessibility": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz",
|
||||
"integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dnd-kit/core": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
|
||||
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@dnd-kit/accessibility": "^3.1.1",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dnd-kit/sortable": {
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz",
|
||||
"integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dnd-kit/core": "^6.3.0",
|
||||
"react": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dnd-kit/utilities": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz",
|
||||
"integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@electric-sql/pglite": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.4.1.tgz",
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@
|
|||
"lint": "eslint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@prisma/adapter-better-sqlite3": "^7.8.0",
|
||||
"@prisma/client": "^7.8.0",
|
||||
"argon2": "^0.44.0",
|
||||
|
|
|
|||
|
|
@ -14,8 +14,9 @@ model Class {
|
|||
sortOrder Int @default(0)
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
decks Deck[]
|
||||
quizSets QuizSet[]
|
||||
decks Deck[]
|
||||
quizSets QuizSet[]
|
||||
materialGroups MaterialGroup[]
|
||||
}
|
||||
|
||||
model Deck {
|
||||
|
|
@ -27,6 +28,8 @@ model Deck {
|
|||
createdAt DateTime @default(now())
|
||||
|
||||
class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
|
||||
groupId String?
|
||||
group MaterialGroup? @relation(fields: [groupId], references: [id], onDelete: SetNull)
|
||||
cards Flashcard[]
|
||||
shareLink ShareLink?
|
||||
progress StudyProgress[]
|
||||
|
|
@ -51,6 +54,8 @@ model QuizSet {
|
|||
createdAt DateTime @default(now())
|
||||
|
||||
class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
|
||||
groupId String?
|
||||
group MaterialGroup? @relation(fields: [groupId], references: [id], onDelete: SetNull)
|
||||
questions Question[]
|
||||
attempts QuizAttempt[]
|
||||
shareLink ShareLink?
|
||||
|
|
@ -113,13 +118,15 @@ model QuizAttempt {
|
|||
|
||||
model ShareLink {
|
||||
id String @id @default(uuid())
|
||||
targetType String // "DECK" | "QUIZ"
|
||||
targetType String // "DECK" | "QUIZ" | "GROUP"
|
||||
deckId String? @unique
|
||||
quizSetId String? @unique
|
||||
groupId String? @unique
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
deck Deck? @relation(fields: [deckId], references: [id], onDelete: Cascade)
|
||||
quizSet QuizSet? @relation(fields: [quizSetId], references: [id], onDelete: Cascade)
|
||||
deck Deck? @relation(fields: [deckId], references: [id], onDelete: Cascade)
|
||||
quizSet QuizSet? @relation(fields: [quizSetId], references: [id], onDelete: Cascade)
|
||||
group MaterialGroup? @relation(fields: [groupId], references: [id], onDelete: Cascade)
|
||||
}
|
||||
|
||||
model AuthSecurity {
|
||||
|
|
@ -133,3 +140,17 @@ model Setting {
|
|||
key String @id
|
||||
value String
|
||||
}
|
||||
|
||||
model MaterialGroup {
|
||||
id String @id @default(uuid())
|
||||
classId String
|
||||
name String
|
||||
type String // "DECK" | "QUIZ"
|
||||
sortOrder Int @default(0)
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
|
||||
decks Deck[]
|
||||
quizSets QuizSet[]
|
||||
shareLink ShareLink?
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,11 +4,36 @@ import { useState, useEffect, useCallback } from "react";
|
|||
import Link from "next/link";
|
||||
import { useParams } from "next/navigation";
|
||||
import { ImportModal } from "@/components/import/ImportModal";
|
||||
import { ShareMenu } from "@/components/ui/ShareMenu";
|
||||
import {
|
||||
DndContext,
|
||||
pointerWithin,
|
||||
KeyboardSensor,
|
||||
PointerSensor,
|
||||
useSensor,
|
||||
useSensors,
|
||||
DragEndEvent,
|
||||
DragOverEvent,
|
||||
DragStartEvent,
|
||||
DragOverlay,
|
||||
useDroppable,
|
||||
useDndContext,
|
||||
} from "@dnd-kit/core";
|
||||
import {
|
||||
arrayMove,
|
||||
SortableContext,
|
||||
sortableKeyboardCoordinates,
|
||||
verticalListSortingStrategy,
|
||||
useSortable,
|
||||
} from "@dnd-kit/sortable";
|
||||
import { CSS } from "@dnd-kit/utilities";
|
||||
|
||||
interface DeckItem {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
groupId: string | null;
|
||||
sortOrder: number;
|
||||
_count: { cards: number };
|
||||
progress: Array<{
|
||||
mode: string;
|
||||
|
|
@ -18,12 +43,126 @@ interface DeckItem {
|
|||
}>;
|
||||
}
|
||||
|
||||
const cache: Record<string, DeckItem[]> = {};
|
||||
interface MaterialGroup {
|
||||
id: string;
|
||||
name: string;
|
||||
sortOrder: number;
|
||||
}
|
||||
|
||||
const cache: Record<string, { decks: DeckItem[]; groups: MaterialGroup[] }> = {};
|
||||
|
||||
function getProgressLabel(deck: DeckItem) {
|
||||
if (!deck.progress?.length) return null;
|
||||
const prog = deck.progress[0];
|
||||
const order = JSON.parse(prog.orderJson) as string[];
|
||||
const results = prog.cardResultsJson
|
||||
? (JSON.parse(prog.cardResultsJson) as Record<string, string>)
|
||||
: {};
|
||||
const correctCount = Object.values(results).filter((r) => r === "correct").length;
|
||||
const total = order.length;
|
||||
const current = Math.min(prog.currentIndex + 1, total);
|
||||
const modeLabel = prog.mode === "SHUFFLED" ? "shuffled" : "sequential";
|
||||
return `${current}/${total}, ${modeLabel} · ${correctCount} ✓`;
|
||||
}
|
||||
|
||||
function SortableDeckCard({ deck, onEdit, onDelete, classSlug }: any) {
|
||||
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: deck.id });
|
||||
const style = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
opacity: isDragging ? 0.4 : 1,
|
||||
};
|
||||
|
||||
const progressLabel = getProgressLabel(deck);
|
||||
|
||||
return (
|
||||
<div ref={setNodeRef} style={style} className="group flex flex-col bg-bg-surface rounded-xl border border-border-light shadow-[var(--shadow-card)] hover:shadow-[var(--shadow-card-hover)] hover:border-primary/20 transition-all duration-300 h-full">
|
||||
<div className="p-4 flex flex-col flex-1 h-full">
|
||||
<div className="flex items-start gap-2">
|
||||
<div {...attributes} {...listeners} className="mt-1 cursor-grab active:cursor-grabbing text-text-muted hover:text-text-heading">
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 8h16M4 16h16" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="text-lg font-semibold text-text-heading mb-1">{deck.name}</h3>
|
||||
{deck.description && <p className="text-sm text-text-secondary mb-3 line-clamp-2">{deck.description}</p>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 mt-auto mb-4 pl-7">
|
||||
<span className="inline-flex items-center gap-1 px-2.5 py-1 rounded-full bg-badge-bg text-badge-text text-xs font-medium">
|
||||
{deck._count.cards} {deck._count.cards === 1 ? "card" : "cards"}
|
||||
</span>
|
||||
{progressLabel && (
|
||||
<span className="text-xs text-text-muted">{progressLabel}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 pt-4 border-t border-border-light pl-7">
|
||||
{deck.progress?.length ? (
|
||||
<>
|
||||
<Link href={`/${classSlug}/flashcards/${deck.id}`} className="flex-1 text-center py-2 px-4 rounded-lg bg-primary text-white text-sm font-medium hover:bg-primary-hover transition-all duration-200">
|
||||
Continue
|
||||
</Link>
|
||||
<button
|
||||
onClick={async () => {
|
||||
if (!confirm("Start over from the beginning? This will clear your current progress for this deck.")) return;
|
||||
await Promise.all([
|
||||
fetch("/api/progress", { method: "DELETE", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ contentType: "DECK", contentId: deck.id, mode: "SEQUENTIAL" }) }).catch(() => {}),
|
||||
fetch("/api/progress", { method: "DELETE", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ contentType: "DECK", contentId: deck.id, mode: "SHUFFLED" }) }).catch(() => {})
|
||||
]);
|
||||
window.location.href = `/${classSlug}/flashcards/${deck.id}`;
|
||||
}}
|
||||
className="p-2 rounded-lg text-text-muted border border-border-light hover:bg-bg-surface-alt hover:text-text-heading transition-all duration-200 cursor-pointer"
|
||||
title="Start New"
|
||||
>
|
||||
<svg className="w-5 h-5 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
</svg>
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<Link href={`/${classSlug}/flashcards/${deck.id}`} className="flex-1 text-center py-2 px-4 rounded-lg bg-primary text-white text-sm font-medium hover:bg-primary-hover transition-all duration-200">
|
||||
Study
|
||||
</Link>
|
||||
)}
|
||||
<button onClick={() => onEdit(deck)} className="p-2 rounded-lg text-text-muted hover:text-text-heading hover:bg-bg-surface-alt transition-all duration-200 cursor-pointer" title="Edit">
|
||||
<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>
|
||||
</button>
|
||||
<button onClick={() => onDelete(deck)} className="p-2 rounded-lg text-text-muted hover:text-error hover:bg-error-bg transition-all duration-200 cursor-pointer" title="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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DroppableContainer({ id, children }: { id: string; children: React.ReactNode }) {
|
||||
const { setNodeRef } = useDroppable({ id });
|
||||
const { over } = useDndContext();
|
||||
|
||||
const isOverContainer = over?.id === id;
|
||||
const isOverChild = over?.data?.current?.sortable?.containerId === id;
|
||||
const isOver = isOverContainer || isOverChild;
|
||||
|
||||
return (
|
||||
<div ref={setNodeRef} className={`grid grid-cols-1 md:grid-cols-2 gap-4 min-h-[60px] p-2 -mx-2 rounded-lg border-2 transition-colors duration-200 ${isOver ? "border-primary/40 bg-primary/5 shadow-inner" : "border-transparent"}`}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function FlashcardsPage() {
|
||||
const params = useParams();
|
||||
const classSlug = params.classSlug as string;
|
||||
const [decks, setDecks] = useState<DeckItem[]>(cache[classSlug] || []);
|
||||
const [decks, setDecks] = useState<DeckItem[]>(cache[classSlug]?.decks || []);
|
||||
const [groups, setGroups] = useState<MaterialGroup[]>(cache[classSlug]?.groups || []);
|
||||
const [loading, setLoading] = useState(!cache[classSlug]);
|
||||
const [animate] = useState(!cache[classSlug]);
|
||||
const [showImport, setShowImport] = useState(false);
|
||||
|
|
@ -31,22 +170,29 @@ export default function FlashcardsPage() {
|
|||
const [editingId, setEditingId] = useState<string | null>(null);
|
||||
const [editName, setEditName] = useState("");
|
||||
const [editDescription, setEditDescription] = useState("");
|
||||
const [isCreatingGroup, setIsCreatingGroup] = useState(false);
|
||||
const [newGroupName, setNewGroupName] = useState("");
|
||||
const [editingGroupId, setEditingGroupId] = useState<string | null>(null);
|
||||
const [editGroupName, setEditGroupName] = useState("");
|
||||
|
||||
const fetchDecks = useCallback(async (cId: string) => {
|
||||
const fetchAll = useCallback(async (cId: string) => {
|
||||
try {
|
||||
const res = await fetch(`/api/decks/list?classId=${cId}`);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
const arr = Array.isArray(data) ? data : [];
|
||||
setDecks(arr);
|
||||
cache[classSlug] = arr;
|
||||
}
|
||||
const [deckRes, groupRes] = await Promise.all([
|
||||
fetch(`/api/decks/list?classId=${cId}`),
|
||||
fetch(`/api/material-groups?classId=${cId}&type=DECK`),
|
||||
]);
|
||||
const ds = deckRes.ok ? await deckRes.json() : [];
|
||||
const gs = groupRes.ok ? await groupRes.json() : [];
|
||||
setDecks(ds);
|
||||
setGroups(gs);
|
||||
cache[classSlug] = { decks: ds, groups: gs };
|
||||
} catch {
|
||||
setDecks([]);
|
||||
setGroups([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
}, [classSlug]);
|
||||
|
||||
useEffect(() => {
|
||||
async function init() {
|
||||
|
|
@ -59,21 +205,56 @@ export default function FlashcardsPage() {
|
|||
return;
|
||||
}
|
||||
setClassId(cls.id);
|
||||
fetchDecks(cls.id);
|
||||
fetchAll(cls.id);
|
||||
} catch {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
init();
|
||||
}, [classSlug, fetchDecks]);
|
||||
}, [classSlug, fetchAll]);
|
||||
|
||||
async function handleDelete(id: string, name: string) {
|
||||
if (!confirm(`Delete "${name}" and all its cards?`)) return;
|
||||
await fetch(`/api/decks/${id}`, { method: "DELETE" });
|
||||
setDecks((prev) => prev.filter((d) => d.id !== id));
|
||||
// Group Management
|
||||
async function handleCreateGroup() {
|
||||
if (!newGroupName.trim()) return;
|
||||
const res = await fetch("/api/material-groups", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ classId, name: newGroupName.trim(), type: "DECK" }),
|
||||
});
|
||||
if (res.ok) {
|
||||
const g = await res.json();
|
||||
setGroups((prev) => [...prev, g]);
|
||||
setIsCreatingGroup(false);
|
||||
setNewGroupName("");
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRename(id: string) {
|
||||
async function handleRenameGroup(id: string) {
|
||||
if (!editGroupName.trim()) return;
|
||||
await fetch(`/api/material-groups/${id}`, {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ name: editGroupName.trim() }),
|
||||
});
|
||||
setGroups((prev) => prev.map((g) => (g.id === id ? { ...g, name: editGroupName.trim() } : g)));
|
||||
setEditingGroupId(null);
|
||||
}
|
||||
|
||||
async function handleDeleteGroup(id: string, name: string) {
|
||||
if (!confirm(`Delete group "${name}"? Decks inside will be moved to Uncategorized.`)) return;
|
||||
await fetch(`/api/material-groups/${id}`, { method: "DELETE" });
|
||||
setGroups((prev) => prev.filter((g) => g.id !== id));
|
||||
setDecks((prev) => prev.map((d) => (d.groupId === id ? { ...d, groupId: null } : d)));
|
||||
}
|
||||
|
||||
// Deck Management
|
||||
async function handleDeleteDeck(deck: DeckItem) {
|
||||
if (!confirm(`Delete "${deck.name}" and all its cards?`)) return;
|
||||
await fetch(`/api/decks/${deck.id}`, { method: "DELETE" });
|
||||
setDecks((prev) => prev.filter((d) => d.id !== deck.id));
|
||||
}
|
||||
|
||||
async function handleRenameDeck(id: string) {
|
||||
if (!editName.trim()) return;
|
||||
await fetch(`/api/decks/${id}`, {
|
||||
method: "PATCH",
|
||||
|
|
@ -86,201 +267,230 @@ export default function FlashcardsPage() {
|
|||
setEditingId(null);
|
||||
}
|
||||
|
||||
function getProgressLabel(deck: DeckItem) {
|
||||
if (!deck.progress?.length) return null;
|
||||
const prog = deck.progress[0];
|
||||
const order = JSON.parse(prog.orderJson) as string[];
|
||||
const results = prog.cardResultsJson
|
||||
? (JSON.parse(prog.cardResultsJson) as Record<string, string>)
|
||||
: {};
|
||||
const correctCount = Object.values(results).filter((r) => r === "correct").length;
|
||||
const total = order.length;
|
||||
const current = Math.min(prog.currentIndex + 1, total);
|
||||
const modeLabel = prog.mode === "SHUFFLED" ? "shuffled" : "sequential";
|
||||
return `${current}/${total}, ${modeLabel} · ${correctCount} ✓`;
|
||||
// Drag and drop setup
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor, { activationConstraint: { distance: 8 } }),
|
||||
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
|
||||
);
|
||||
|
||||
const [activeId, setActiveId] = useState<string | null>(null);
|
||||
|
||||
function handleDragStart(event: DragStartEvent) {
|
||||
setActiveId(event.active.id as string);
|
||||
}
|
||||
|
||||
function handleDragOver(event: DragOverEvent) {
|
||||
// optional layout updates here
|
||||
}
|
||||
|
||||
async function handleDragEnd(event: DragEndEvent) {
|
||||
setActiveId(null);
|
||||
const { active, over } = event;
|
||||
if (!over) return;
|
||||
|
||||
const activeId = active.id as string;
|
||||
const overId = over.id as string;
|
||||
|
||||
const activeDeck = decks.find((d) => d.id === activeId);
|
||||
if (!activeDeck) return;
|
||||
|
||||
let targetGroupId: string | null = null;
|
||||
let targetIndex = 0;
|
||||
|
||||
const overContainerId = over.data.current?.sortable?.containerId;
|
||||
if (overContainerId) {
|
||||
targetGroupId = overContainerId === "uncategorized" ? null : overContainerId;
|
||||
} else if (groups.find(g => g.id === overId) || overId === "uncategorized") {
|
||||
targetGroupId = overId === "uncategorized" ? null : overId;
|
||||
} else {
|
||||
const overDeck = decks.find((d) => d.id === overId);
|
||||
if (overDeck) {
|
||||
targetGroupId = overDeck.groupId;
|
||||
}
|
||||
}
|
||||
|
||||
if (targetGroupId !== undefined) {
|
||||
setDecks((items) => {
|
||||
const oldIndex = items.findIndex((d) => d.id === activeId);
|
||||
const overIndex = items.findIndex((d) => d.id === overId);
|
||||
let newItems = [...items];
|
||||
|
||||
newItems[oldIndex].groupId = targetGroupId;
|
||||
|
||||
if (overIndex >= 0 && overIndex !== oldIndex) {
|
||||
newItems = arrayMove(newItems, oldIndex, overIndex);
|
||||
} else {
|
||||
const oldItem = newItems.splice(oldIndex, 1)[0];
|
||||
newItems.push(oldItem);
|
||||
}
|
||||
|
||||
const affectedGroups = new Set([activeDeck.groupId, targetGroupId]);
|
||||
const updates: any[] = [];
|
||||
|
||||
affectedGroups.forEach(gId => {
|
||||
const gItems = newItems.filter(d => d.groupId === gId);
|
||||
gItems.forEach((item, index) => {
|
||||
item.sortOrder = index;
|
||||
updates.push({ id: item.id, sortOrder: index, groupId: item.groupId });
|
||||
});
|
||||
});
|
||||
|
||||
fetch("/api/decks/reorder", {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ items: updates }),
|
||||
});
|
||||
|
||||
return newItems;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const groupedDecks = groups.map(g => ({
|
||||
...g,
|
||||
decks: decks.filter(d => d.groupId === g.id).sort((a,b) => a.sortOrder - b.sortOrder)
|
||||
}));
|
||||
const uncategorizedDecks = decks.filter(d => d.groupId === null).sort((a,b) => a.sortOrder - b.sortOrder);
|
||||
|
||||
const activeDeck = decks.find(d => d.id === activeId);
|
||||
|
||||
return (
|
||||
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 pb-20">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h2 className="text-xl font-semibold text-text-heading">Flashcard Decks</h2>
|
||||
<button
|
||||
onClick={() => setShowImport(true)}
|
||||
className="inline-flex items-center gap-2 px-4 py-2.5 rounded-lg bg-primary text-white font-medium hover:bg-primary-hover transition-all duration-200 shadow-sm cursor-pointer"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
|
||||
</svg>
|
||||
Import Deck
|
||||
</button>
|
||||
<div className="flex items-center gap-3">
|
||||
<button onClick={() => setIsCreatingGroup(true)} className="inline-flex items-center gap-2 px-4 py-2.5 rounded-lg bg-bg-surface-alt text-text-heading font-medium border border-border-light hover:bg-border transition-all duration-200 shadow-sm cursor-pointer">
|
||||
Add Group
|
||||
</button>
|
||||
<button onClick={() => setShowImport(true)} className="inline-flex items-center gap-2 px-4 py-2.5 rounded-lg bg-primary text-white font-medium hover:bg-primary-hover transition-all duration-200 shadow-sm cursor-pointer">
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
|
||||
</svg>
|
||||
Import Deck
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{/* Empty state */}
|
||||
{!loading && decks.length === 0 && (
|
||||
<div className={`text-center py-16 ${animate ? "animate-slide-up" : ""}`}>
|
||||
<div className="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-bg-callout mb-4">
|
||||
<svg className="w-8 h-8 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-text-heading mb-1">No flashcard decks yet</h3>
|
||||
<p className="text-text-secondary mb-4">Import your first deck to start studying</p>
|
||||
<button
|
||||
onClick={() => setShowImport(true)}
|
||||
className="inline-flex items-center gap-2 px-4 py-2.5 rounded-lg bg-primary text-white font-medium hover:bg-primary-hover transition-all duration-200 cursor-pointer"
|
||||
>
|
||||
Import Deck
|
||||
{isCreatingGroup && (
|
||||
<div className="mb-6 p-4 bg-bg-surface rounded-xl border border-primary/20 shadow-sm flex items-center gap-3">
|
||||
<input
|
||||
autoFocus
|
||||
type="text"
|
||||
value={newGroupName}
|
||||
onChange={(e) => setNewGroupName(e.target.value)}
|
||||
placeholder="e.g. Exam 1 Flashcards"
|
||||
className="flex-1 px-3 py-2 rounded-lg border border-border bg-bg-surface-alt/50 text-text-heading focus:outline-none focus:ring-2 focus:ring-primary/30"
|
||||
/>
|
||||
<button onClick={handleCreateGroup} className="px-4 py-2 bg-primary text-white font-medium rounded-md hover:bg-primary-hover cursor-pointer">
|
||||
Save
|
||||
</button>
|
||||
<button onClick={() => { setIsCreatingGroup(false); setNewGroupName(""); }} className="px-4 py-2 bg-bg-surface-alt text-text-heading font-medium rounded-md hover:bg-border cursor-pointer">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Deck grid */}
|
||||
{!loading && decks.length > 0 && (
|
||||
<div className={`grid grid-cols-1 md:grid-cols-2 gap-4 ${animate ? "animate-slide-up" : ""}`}>
|
||||
{decks.map((deck) => {
|
||||
const progressLabel = getProgressLabel(deck);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={deck.id}
|
||||
className="group flex flex-col bg-bg-surface rounded-xl border border-border-light shadow-[var(--shadow-card)] hover:shadow-[var(--shadow-card-hover)] hover:border-primary/20 transition-all duration-300 h-full"
|
||||
>
|
||||
<div className="p-6 flex flex-col flex-1 h-full">
|
||||
{/* Name and Description (editable) */}
|
||||
{editingId === deck.id ? (
|
||||
<div className="flex flex-col gap-3 mb-3">
|
||||
<input
|
||||
type="text"
|
||||
value={editName}
|
||||
onChange={(e) => setEditName(e.target.value)}
|
||||
placeholder="Deck Name"
|
||||
autoFocus
|
||||
className="w-full px-3 py-1.5 rounded-lg border border-border bg-bg-surface-alt/50 text-text-heading text-sm font-semibold focus:outline-none focus:ring-2 focus:ring-primary/30"
|
||||
/>
|
||||
<textarea
|
||||
value={editDescription}
|
||||
onChange={(e) => setEditDescription(e.target.value)}
|
||||
placeholder="Description (optional)"
|
||||
rows={2}
|
||||
className="w-full px-3 py-1.5 rounded-lg border border-border bg-bg-surface-alt/50 text-text-heading text-sm focus:outline-none focus:ring-2 focus:ring-primary/30 resize-none"
|
||||
/>
|
||||
<div className="flex gap-2 mt-1">
|
||||
<button
|
||||
onClick={() => handleRename(deck.id)}
|
||||
className="px-3 py-1.5 bg-primary text-white text-xs font-medium rounded-md hover:bg-primary-hover cursor-pointer"
|
||||
>
|
||||
Save Changes
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setEditingId(null)}
|
||||
className="px-3 py-1.5 bg-bg-surface-alt text-text-heading text-xs font-medium rounded-md hover:bg-border cursor-pointer transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<h3 className="text-lg font-semibold text-text-heading mb-1">
|
||||
{deck.name}
|
||||
</h3>
|
||||
{deck.description && (
|
||||
<p className="text-sm text-text-secondary mb-3 line-clamp-2">{deck.description}</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Stats */}
|
||||
<div className="flex items-center gap-3 mt-auto mb-4">
|
||||
<span className="inline-flex items-center gap-1 px-2.5 py-1 rounded-full bg-badge-bg text-badge-text text-xs font-medium">
|
||||
{deck._count.cards} {deck._count.cards === 1 ? "card" : "cards"}
|
||||
</span>
|
||||
{progressLabel && (
|
||||
<span className="text-xs text-text-muted">{progressLabel}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex items-center gap-2 pt-4 border-t border-border-light">
|
||||
{deck.progress?.length ? (
|
||||
<>
|
||||
<Link
|
||||
href={`/${classSlug}/flashcards/${deck.id}`}
|
||||
className="flex-1 text-center py-2 px-4 rounded-lg bg-primary text-white text-sm font-medium hover:bg-primary-hover transition-all duration-200"
|
||||
>
|
||||
Continue
|
||||
</Link>
|
||||
<button
|
||||
onClick={async () => {
|
||||
if (!confirm("Start over from the beginning? This will clear your current progress for this deck.")) return;
|
||||
await Promise.all([
|
||||
fetch("/api/progress", { method: "DELETE", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ contentType: "DECK", contentId: deck.id, mode: "SEQUENTIAL" }) }).catch(() => {}),
|
||||
fetch("/api/progress", { method: "DELETE", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ contentType: "DECK", contentId: deck.id, mode: "SHUFFLED" }) }).catch(() => {})
|
||||
]);
|
||||
window.location.href = `/${classSlug}/flashcards/${deck.id}`;
|
||||
}}
|
||||
className="p-2 rounded-lg text-text-muted border border-border-light hover:bg-bg-surface-alt hover:text-text-heading transition-all duration-200 cursor-pointer"
|
||||
title="Start New"
|
||||
>
|
||||
<svg className="w-5 h-5 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
</svg>
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<Link
|
||||
href={`/${classSlug}/flashcards/${deck.id}`}
|
||||
className="flex-1 text-center py-2 px-4 rounded-lg bg-primary text-white text-sm font-medium hover:bg-primary-hover transition-all duration-200"
|
||||
>
|
||||
Study
|
||||
</Link>
|
||||
)}
|
||||
<button
|
||||
onClick={() => {
|
||||
setEditingId(deck.id);
|
||||
setEditName(deck.name);
|
||||
setEditDescription(deck.description || "");
|
||||
}}
|
||||
className="p-2 rounded-lg text-text-muted hover:text-text-heading hover:bg-bg-surface-alt transition-all duration-200 cursor-pointer"
|
||||
title="Edit"
|
||||
>
|
||||
<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>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDelete(deck.id, deck.name)}
|
||||
className="p-2 rounded-lg text-text-muted hover:text-error hover:bg-error-bg transition-all duration-200 cursor-pointer"
|
||||
title="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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{editingId && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/40 backdrop-blur-sm">
|
||||
<div className="bg-bg-surface rounded-xl p-6 w-full max-w-md shadow-lg border border-border">
|
||||
<h3 className="text-lg font-semibold text-text-heading mb-4">Edit Deck</h3>
|
||||
<input
|
||||
type="text"
|
||||
value={editName}
|
||||
onChange={(e) => setEditName(e.target.value)}
|
||||
placeholder="Deck Name"
|
||||
autoFocus
|
||||
className="w-full px-3 py-2 mb-3 rounded-lg border border-border bg-bg-surface-alt/50 text-text-heading focus:outline-none focus:ring-2 focus:ring-primary/30"
|
||||
/>
|
||||
<textarea
|
||||
value={editDescription}
|
||||
onChange={(e) => setEditDescription(e.target.value)}
|
||||
placeholder="Description (optional)"
|
||||
rows={3}
|
||||
className="w-full px-3 py-2 mb-4 rounded-lg border border-border bg-bg-surface-alt/50 text-text-heading focus:outline-none focus:ring-2 focus:ring-primary/30 resize-none"
|
||||
/>
|
||||
<div className="flex gap-2 justify-end">
|
||||
<button onClick={() => setEditingId(null)} className="px-4 py-2 bg-bg-surface-alt text-text-heading font-medium rounded-md hover:bg-border cursor-pointer">Cancel</button>
|
||||
<button onClick={() => handleRenameDeck(editingId)} className="px-4 py-2 bg-primary text-white font-medium rounded-md hover:bg-primary-hover cursor-pointer">Save Changes</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Import Modal */}
|
||||
{!loading && decks.length === 0 && groups.length === 0 && (
|
||||
<div className="text-center py-16">
|
||||
<h3 className="text-lg font-semibold text-text-heading mb-1">No decks yet</h3>
|
||||
<p className="text-text-secondary mb-4">Import your first deck or create a group</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!loading && (
|
||||
<DndContext sensors={sensors} collisionDetection={pointerWithin} onDragStart={handleDragStart} onDragOver={handleDragOver} onDragEnd={handleDragEnd}>
|
||||
<div className="space-y-8">
|
||||
{groupedDecks.map(group => (
|
||||
<div key={group.id} className="bg-bg-surface-alt/30 p-4 rounded-xl border border-border-light">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
{editingGroupId === group.id ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<input autoFocus value={editGroupName} onChange={e => setEditGroupName(e.target.value)} className="px-2 py-1 rounded border border-border bg-bg-surface text-text-heading focus:ring-1 focus:ring-primary" />
|
||||
<button onClick={() => handleRenameGroup(group.id)} className="text-xs px-2 py-1 bg-primary text-white rounded">Save</button>
|
||||
<button onClick={() => setEditingGroupId(null)} className="text-xs px-2 py-1 bg-bg-surface text-text-heading rounded border border-border-light">Cancel</button>
|
||||
</div>
|
||||
) : (
|
||||
<h3 className="text-lg font-semibold text-text-heading">{group.name}</h3>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<ShareMenu targetType="GROUP" contentId={group.id} classSlug={params.classSlug as string} />
|
||||
<button onClick={() => { setEditingGroupId(group.id); setEditGroupName(group.name); }} className="text-text-muted hover:text-text-heading"><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></button>
|
||||
<button onClick={() => handleDeleteGroup(group.id, group.name)} className="text-text-muted hover:text-error"><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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SortableContext id={group.id} items={group.decks.map(d => d.id)} strategy={verticalListSortingStrategy}>
|
||||
<DroppableContainer id={group.id}>
|
||||
{group.decks.length === 0 ? (
|
||||
<div className="col-span-1 md:col-span-2 text-center py-4 text-text-muted text-sm border-2 border-dashed border-border-light rounded-lg">Drop decks here</div>
|
||||
) : (
|
||||
group.decks.map(deck => (
|
||||
<SortableDeckCard key={deck.id} deck={deck} onEdit={(d: DeckItem) => { setEditingId(d.id); setEditName(d.name); setEditDescription(d.description || ""); }} onDelete={handleDeleteDeck} classSlug={classSlug} />
|
||||
))
|
||||
)}
|
||||
</DroppableContainer>
|
||||
</SortableContext>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Uncategorized */}
|
||||
<div className="bg-bg-surface-alt/30 p-4 rounded-xl border border-border-light">
|
||||
<h3 className="text-lg font-semibold text-text-heading mb-4">Uncategorized</h3>
|
||||
<SortableContext id="uncategorized" items={uncategorizedDecks.map(d => d.id)} strategy={verticalListSortingStrategy}>
|
||||
<DroppableContainer id="uncategorized">
|
||||
{uncategorizedDecks.length === 0 ? (
|
||||
<div className="col-span-1 md:col-span-2 text-center py-4 text-text-muted text-sm border-2 border-dashed border-border-light rounded-lg">No uncategorized decks</div>
|
||||
) : (
|
||||
uncategorizedDecks.map(deck => (
|
||||
<SortableDeckCard key={deck.id} deck={deck} onEdit={(d: DeckItem) => { setEditingId(d.id); setEditName(d.name); setEditDescription(d.description || ""); }} onDelete={handleDeleteDeck} classSlug={classSlug} />
|
||||
))
|
||||
)}
|
||||
</DroppableContainer>
|
||||
</SortableContext>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DragOverlay>
|
||||
{activeDeck ? (
|
||||
<div className="opacity-80 scale-105 shadow-xl rotate-2">
|
||||
<SortableDeckCard deck={activeDeck} onEdit={()=>{}} onDelete={()=>{}} classSlug={classSlug} />
|
||||
</div>
|
||||
) : null}
|
||||
</DragOverlay>
|
||||
</DndContext>
|
||||
)}
|
||||
|
||||
{showImport && (
|
||||
<ImportModal
|
||||
classId={classId}
|
||||
importType="flashcards"
|
||||
onClose={() => setShowImport(false)}
|
||||
onImported={() => {
|
||||
setShowImport(false);
|
||||
setLoading(true);
|
||||
// Re-fetch
|
||||
window.location.reload();
|
||||
}}
|
||||
/>
|
||||
<ImportModal classId={classId} importType="flashcards" onClose={() => setShowImport(false)} onImported={() => { setShowImport(false); setLoading(true); window.location.reload(); }} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,20 +4,121 @@ import { useState, useEffect, useCallback } from "react";
|
|||
import Link from "next/link";
|
||||
import { useParams } from "next/navigation";
|
||||
import { ImportModal } from "@/components/import/ImportModal";
|
||||
import { ShareMenu } from "@/components/ui/ShareMenu";
|
||||
import {
|
||||
DndContext,
|
||||
pointerWithin,
|
||||
KeyboardSensor,
|
||||
PointerSensor,
|
||||
useSensor,
|
||||
useSensors,
|
||||
DragEndEvent,
|
||||
DragOverEvent,
|
||||
DragStartEvent,
|
||||
DragOverlay,
|
||||
useDroppable,
|
||||
useDndContext,
|
||||
} from "@dnd-kit/core";
|
||||
import {
|
||||
arrayMove,
|
||||
SortableContext,
|
||||
sortableKeyboardCoordinates,
|
||||
verticalListSortingStrategy,
|
||||
useSortable,
|
||||
} from "@dnd-kit/sortable";
|
||||
import { CSS } from "@dnd-kit/utilities";
|
||||
|
||||
interface QuizItem {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
groupId: string | null;
|
||||
sortOrder: number;
|
||||
_count: { questions: number; attempts: number };
|
||||
}
|
||||
|
||||
const cache: Record<string, QuizItem[]> = {};
|
||||
interface MaterialGroup {
|
||||
id: string;
|
||||
name: string;
|
||||
sortOrder: number;
|
||||
}
|
||||
|
||||
const cache: Record<string, { quizzes: QuizItem[]; groups: MaterialGroup[] }> = {};
|
||||
|
||||
function SortableQuizCard({ quiz, onEdit, onDelete, classSlug }: any) {
|
||||
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: quiz.id });
|
||||
const style = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
opacity: isDragging ? 0.4 : 1,
|
||||
};
|
||||
|
||||
return (
|
||||
<div ref={setNodeRef} style={style} className="group bg-bg-surface rounded-xl border border-border-light shadow-[var(--shadow-card)] hover:shadow-[var(--shadow-card-hover)] hover:border-primary/20 transition-all duration-300">
|
||||
<div className="p-4 flex flex-col flex-1 h-full">
|
||||
<div className="flex items-start gap-2">
|
||||
<div {...attributes} {...listeners} className="mt-1 cursor-grab active:cursor-grabbing text-text-muted hover:text-text-heading">
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 8h16M4 16h16" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="text-lg font-semibold text-text-heading mb-1">{quiz.name}</h3>
|
||||
{quiz.description && <p className="text-sm text-text-secondary mb-3 line-clamp-2">{quiz.description}</p>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 mt-auto pl-7">
|
||||
<span className="inline-flex items-center gap-1 px-2.5 py-1 rounded-full bg-badge-bg text-badge-text text-xs font-medium">
|
||||
{quiz._count.questions} {quiz._count.questions === 1 ? "question" : "questions"}
|
||||
</span>
|
||||
{quiz._count.attempts > 0 && (
|
||||
<span className="text-xs text-text-muted">
|
||||
{quiz._count.attempts} {quiz._count.attempts === 1 ? "attempt" : "attempts"}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 mt-4 pt-4 border-t border-border-light pl-7">
|
||||
<Link href={`/${classSlug}/quizzes/${quiz.id}`} className="flex-1 text-center py-2 px-4 rounded-lg bg-primary text-white text-sm font-medium hover:bg-primary-hover transition-all duration-200">
|
||||
Start Quiz
|
||||
</Link>
|
||||
<button onClick={() => onEdit(quiz)} className="p-2 rounded-lg text-text-muted hover:text-text-heading hover:bg-bg-surface-alt transition-all duration-200 cursor-pointer" title="Edit">
|
||||
<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>
|
||||
</button>
|
||||
<button onClick={() => onDelete(quiz)} className="p-2 rounded-lg text-text-muted hover:text-error hover:bg-error-bg transition-all duration-200 cursor-pointer" title="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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DroppableContainer({ id, children }: { id: string; children: React.ReactNode }) {
|
||||
const { setNodeRef } = useDroppable({ id });
|
||||
const { over } = useDndContext();
|
||||
|
||||
const isOverContainer = over?.id === id;
|
||||
const isOverChild = over?.data?.current?.sortable?.containerId === id;
|
||||
const isOver = isOverContainer || isOverChild;
|
||||
|
||||
return (
|
||||
<div ref={setNodeRef} className={`grid grid-cols-1 md:grid-cols-2 gap-4 min-h-[60px] p-2 -mx-2 rounded-lg border-2 transition-colors duration-200 ${isOver ? "border-primary/40 bg-primary/5 shadow-inner" : "border-transparent"}`}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function QuizzesPage() {
|
||||
const params = useParams();
|
||||
const classSlug = params.classSlug as string;
|
||||
const [quizzes, setQuizzes] = useState<QuizItem[]>(cache[classSlug] || []);
|
||||
const [quizzes, setQuizzes] = useState<QuizItem[]>(cache[classSlug]?.quizzes || []);
|
||||
const [groups, setGroups] = useState<MaterialGroup[]>(cache[classSlug]?.groups || []);
|
||||
const [loading, setLoading] = useState(!cache[classSlug]);
|
||||
const [animate] = useState(!cache[classSlug]);
|
||||
const [showImport, setShowImport] = useState(false);
|
||||
|
|
@ -25,22 +126,29 @@ export default function QuizzesPage() {
|
|||
const [editingId, setEditingId] = useState<string | null>(null);
|
||||
const [editName, setEditName] = useState("");
|
||||
const [editDescription, setEditDescription] = useState("");
|
||||
const [isCreatingGroup, setIsCreatingGroup] = useState(false);
|
||||
const [newGroupName, setNewGroupName] = useState("");
|
||||
const [editingGroupId, setEditingGroupId] = useState<string | null>(null);
|
||||
const [editGroupName, setEditGroupName] = useState("");
|
||||
|
||||
const fetchQuizzes = useCallback(async (cId: string) => {
|
||||
const fetchAll = useCallback(async (cId: string) => {
|
||||
try {
|
||||
const res = await fetch(`/api/quizzes/list?classId=${cId}`);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
const arr = Array.isArray(data) ? data : [];
|
||||
setQuizzes(arr);
|
||||
cache[classSlug] = arr;
|
||||
}
|
||||
const [quizRes, groupRes] = await Promise.all([
|
||||
fetch(`/api/quizzes/list?classId=${cId}`),
|
||||
fetch(`/api/material-groups?classId=${cId}&type=QUIZ`),
|
||||
]);
|
||||
const qs = quizRes.ok ? await quizRes.json() : [];
|
||||
const gs = groupRes.ok ? await groupRes.json() : [];
|
||||
setQuizzes(qs);
|
||||
setGroups(gs);
|
||||
cache[classSlug] = { quizzes: qs, groups: gs };
|
||||
} catch {
|
||||
setQuizzes([]);
|
||||
setGroups([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
}, [classSlug]);
|
||||
|
||||
useEffect(() => {
|
||||
async function init() {
|
||||
|
|
@ -53,21 +161,56 @@ export default function QuizzesPage() {
|
|||
return;
|
||||
}
|
||||
setClassId(cls.id);
|
||||
fetchQuizzes(cls.id);
|
||||
fetchAll(cls.id);
|
||||
} catch {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
init();
|
||||
}, [classSlug, fetchQuizzes]);
|
||||
}, [classSlug, fetchAll]);
|
||||
|
||||
async function handleDelete(id: string, name: string) {
|
||||
if (!confirm(`Delete "${name}" and all its questions and attempts?`)) return;
|
||||
await fetch(`/api/quizzes/${id}`, { method: "DELETE" });
|
||||
setQuizzes((prev) => prev.filter((q) => q.id !== id));
|
||||
// Group Management
|
||||
async function handleCreateGroup() {
|
||||
if (!newGroupName.trim()) return;
|
||||
const res = await fetch("/api/material-groups", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ classId, name: newGroupName.trim(), type: "QUIZ" }),
|
||||
});
|
||||
if (res.ok) {
|
||||
const g = await res.json();
|
||||
setGroups((prev) => [...prev, g]);
|
||||
setIsCreatingGroup(false);
|
||||
setNewGroupName("");
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRename(id: string) {
|
||||
async function handleRenameGroup(id: string) {
|
||||
if (!editGroupName.trim()) return;
|
||||
await fetch(`/api/material-groups/${id}`, {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ name: editGroupName.trim() }),
|
||||
});
|
||||
setGroups((prev) => prev.map((g) => (g.id === id ? { ...g, name: editGroupName.trim() } : g)));
|
||||
setEditingGroupId(null);
|
||||
}
|
||||
|
||||
async function handleDeleteGroup(id: string, name: string) {
|
||||
if (!confirm(`Delete group "${name}"? Quizzes inside will be moved to Uncategorized.`)) return;
|
||||
await fetch(`/api/material-groups/${id}`, { method: "DELETE" });
|
||||
setGroups((prev) => prev.filter((g) => g.id !== id));
|
||||
setQuizzes((prev) => prev.map((q) => (q.groupId === id ? { ...q, groupId: null } : q)));
|
||||
}
|
||||
|
||||
// Quiz Management
|
||||
async function handleDeleteQuiz(quiz: QuizItem) {
|
||||
if (!confirm(`Delete "${quiz.name}" and all its questions and attempts?`)) return;
|
||||
await fetch(`/api/quizzes/${quiz.id}`, { method: "DELETE" });
|
||||
setQuizzes((prev) => prev.filter((q) => q.id !== quiz.id));
|
||||
}
|
||||
|
||||
async function handleRenameQuiz(id: string) {
|
||||
if (!editName.trim()) return;
|
||||
await fetch(`/api/quizzes/${id}`, {
|
||||
method: "PATCH",
|
||||
|
|
@ -80,155 +223,247 @@ export default function QuizzesPage() {
|
|||
setEditingId(null);
|
||||
}
|
||||
|
||||
// Drag and drop setup
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor, { activationConstraint: { distance: 8 } }),
|
||||
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
|
||||
);
|
||||
|
||||
const [activeId, setActiveId] = useState<string | null>(null);
|
||||
|
||||
function handleDragStart(event: DragStartEvent) {
|
||||
setActiveId(event.active.id as string);
|
||||
}
|
||||
|
||||
function handleDragOver(event: DragOverEvent) {
|
||||
const { active, over } = event;
|
||||
if (!over) return;
|
||||
|
||||
const activeId = active.id as string;
|
||||
const overId = over.id as string;
|
||||
|
||||
if (activeId === overId) return;
|
||||
|
||||
// Is it dropping over a container directly?
|
||||
const isOverContainer = over.data.current?.sortable?.containerId || overId;
|
||||
// ... we handle layout updates in dragEnd to keep it simple, or here for smooth lists.
|
||||
// For simplicity, we just handle the final move in dragEnd.
|
||||
}
|
||||
|
||||
async function handleDragEnd(event: DragEndEvent) {
|
||||
setActiveId(null);
|
||||
const { active, over } = event;
|
||||
if (!over) return;
|
||||
|
||||
const activeId = active.id as string;
|
||||
const overId = over.id as string;
|
||||
|
||||
const activeQuiz = quizzes.find((q) => q.id === activeId);
|
||||
if (!activeQuiz) return;
|
||||
|
||||
let targetGroupId: string | null = null;
|
||||
let targetIndex = 0;
|
||||
|
||||
// Check if over a group container directly
|
||||
const overContainerId = over.data.current?.sortable?.containerId;
|
||||
if (overContainerId) {
|
||||
targetGroupId = overContainerId === "uncategorized" ? null : overContainerId;
|
||||
} else if (groups.find(g => g.id === overId) || overId === "uncategorized") {
|
||||
targetGroupId = overId === "uncategorized" ? null : overId;
|
||||
} else {
|
||||
const overQuiz = quizzes.find((q) => q.id === overId);
|
||||
if (overQuiz) {
|
||||
targetGroupId = overQuiz.groupId;
|
||||
}
|
||||
}
|
||||
|
||||
if (targetGroupId !== undefined) {
|
||||
setQuizzes((items) => {
|
||||
const oldIndex = items.findIndex((q) => q.id === activeId);
|
||||
const overIndex = items.findIndex((q) => q.id === overId);
|
||||
let newItems = [...items];
|
||||
|
||||
newItems[oldIndex].groupId = targetGroupId;
|
||||
|
||||
if (overIndex >= 0 && overIndex !== oldIndex) {
|
||||
newItems = arrayMove(newItems, oldIndex, overIndex);
|
||||
} else {
|
||||
// just moved to end of a group
|
||||
const oldItem = newItems.splice(oldIndex, 1)[0];
|
||||
newItems.push(oldItem);
|
||||
}
|
||||
|
||||
// Re-calculate sortOrder for the affected groups to persist
|
||||
const affectedGroups = new Set([activeQuiz.groupId, targetGroupId]);
|
||||
const updates: any[] = [];
|
||||
|
||||
affectedGroups.forEach(gId => {
|
||||
const gItems = newItems.filter(q => q.groupId === gId);
|
||||
gItems.forEach((item, index) => {
|
||||
item.sortOrder = index;
|
||||
updates.push({ id: item.id, sortOrder: index, groupId: item.groupId });
|
||||
});
|
||||
});
|
||||
|
||||
// Fire API
|
||||
fetch("/api/quizzes/reorder", {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ items: updates }),
|
||||
});
|
||||
|
||||
return newItems;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Derive grouped quizzes
|
||||
const groupedQuizzes = groups.map(g => ({
|
||||
...g,
|
||||
quizzes: quizzes.filter(q => q.groupId === g.id).sort((a,b) => a.sortOrder - b.sortOrder)
|
||||
}));
|
||||
const uncategorizedQuizzes = quizzes.filter(q => q.groupId === null).sort((a,b) => a.sortOrder - b.sortOrder);
|
||||
|
||||
const activeQuiz = quizzes.find(q => q.id === activeId);
|
||||
|
||||
return (
|
||||
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 pb-20">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h2 className="text-xl font-semibold text-text-heading">Quizzes</h2>
|
||||
<button
|
||||
onClick={() => setShowImport(true)}
|
||||
className="inline-flex items-center gap-2 px-4 py-2.5 rounded-lg bg-primary text-white font-medium hover:bg-primary-hover transition-all duration-200 shadow-sm cursor-pointer"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
|
||||
</svg>
|
||||
Import Quiz
|
||||
</button>
|
||||
<div className="flex items-center gap-3">
|
||||
<button onClick={() => setIsCreatingGroup(true)} className="inline-flex items-center gap-2 px-4 py-2.5 rounded-lg bg-bg-surface-alt text-text-heading font-medium border border-border-light hover:bg-border transition-all duration-200 shadow-sm cursor-pointer">
|
||||
Add Group
|
||||
</button>
|
||||
<button onClick={() => setShowImport(true)} className="inline-flex items-center gap-2 px-4 py-2.5 rounded-lg bg-primary text-white font-medium hover:bg-primary-hover transition-all duration-200 shadow-sm cursor-pointer">
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
|
||||
</svg>
|
||||
Import Quiz
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{/* Empty state */}
|
||||
{!loading && quizzes.length === 0 && (
|
||||
<div className={`text-center py-16 ${animate ? "animate-slide-up" : ""}`}>
|
||||
<div className="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-bg-callout mb-4">
|
||||
<svg className="w-8 h-8 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-text-heading mb-1">No quizzes yet</h3>
|
||||
<p className="text-text-secondary mb-4">Import your first quiz to start practicing</p>
|
||||
<button
|
||||
onClick={() => setShowImport(true)}
|
||||
className="inline-flex items-center gap-2 px-4 py-2.5 rounded-lg bg-primary text-white font-medium hover:bg-primary-hover transition-all duration-200 cursor-pointer"
|
||||
>
|
||||
Import Quiz
|
||||
{isCreatingGroup && (
|
||||
<div className="mb-6 p-4 bg-bg-surface rounded-xl border border-primary/20 shadow-sm flex items-center gap-3">
|
||||
<input
|
||||
autoFocus
|
||||
type="text"
|
||||
value={newGroupName}
|
||||
onChange={(e) => setNewGroupName(e.target.value)}
|
||||
placeholder="e.g. Exam 1 Quizzes"
|
||||
className="flex-1 px-3 py-2 rounded-lg border border-border bg-bg-surface-alt/50 text-text-heading focus:outline-none focus:ring-2 focus:ring-primary/30"
|
||||
/>
|
||||
<button onClick={handleCreateGroup} className="px-4 py-2 bg-primary text-white font-medium rounded-md hover:bg-primary-hover cursor-pointer">
|
||||
Save
|
||||
</button>
|
||||
<button onClick={() => { setIsCreatingGroup(false); setNewGroupName(""); }} className="px-4 py-2 bg-bg-surface-alt text-text-heading font-medium rounded-md hover:bg-border cursor-pointer">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Quiz grid */}
|
||||
{!loading && quizzes.length > 0 && (
|
||||
<div className={`grid grid-cols-1 md:grid-cols-2 gap-4 ${animate ? "animate-slide-up" : ""}`}>
|
||||
{quizzes.map((quiz) => (
|
||||
<div
|
||||
key={quiz.id}
|
||||
className="group bg-bg-surface rounded-xl border border-border-light shadow-[var(--shadow-card)] hover:shadow-[var(--shadow-card-hover)] hover:border-primary/20 transition-all duration-300"
|
||||
>
|
||||
<div className="p-6 flex flex-col flex-1 h-full">
|
||||
{/* Name and Description (editable) */}
|
||||
{editingId === quiz.id ? (
|
||||
<div className="flex flex-col gap-3 mb-3">
|
||||
<input
|
||||
type="text"
|
||||
value={editName}
|
||||
onChange={(e) => setEditName(e.target.value)}
|
||||
placeholder="Quiz Name"
|
||||
autoFocus
|
||||
className="w-full px-3 py-1.5 rounded-lg border border-border bg-bg-surface-alt/50 text-text-heading text-sm font-semibold focus:outline-none focus:ring-2 focus:ring-primary/30"
|
||||
/>
|
||||
<textarea
|
||||
value={editDescription}
|
||||
onChange={(e) => setEditDescription(e.target.value)}
|
||||
placeholder="Description (optional)"
|
||||
rows={2}
|
||||
className="w-full px-3 py-1.5 rounded-lg border border-border bg-bg-surface-alt/50 text-text-heading text-sm focus:outline-none focus:ring-2 focus:ring-primary/30 resize-none"
|
||||
/>
|
||||
<div className="flex gap-2 mt-1">
|
||||
<button
|
||||
onClick={() => handleRename(quiz.id)}
|
||||
className="px-3 py-1.5 bg-primary text-white text-xs font-medium rounded-md hover:bg-primary-hover cursor-pointer"
|
||||
>
|
||||
Save Changes
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setEditingId(null)}
|
||||
className="px-3 py-1.5 bg-bg-surface-alt text-text-heading text-xs font-medium rounded-md hover:bg-border cursor-pointer transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<h3 className="text-lg font-semibold text-text-heading mb-1">
|
||||
{quiz.name}
|
||||
</h3>
|
||||
{quiz.description && (
|
||||
<p className="text-sm text-text-secondary mb-3 line-clamp-2">
|
||||
{quiz.description}
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<div className="flex items-center gap-3 mt-auto">
|
||||
<span className="inline-flex items-center gap-1 px-2.5 py-1 rounded-full bg-badge-bg text-badge-text text-xs font-medium">
|
||||
{quiz._count.questions} {quiz._count.questions === 1 ? "question" : "questions"}
|
||||
</span>
|
||||
{quiz._count.attempts > 0 && (
|
||||
<span className="text-xs text-text-muted">
|
||||
{quiz._count.attempts} {quiz._count.attempts === 1 ? "attempt" : "attempts"}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 mt-4 pt-4 border-t border-border-light">
|
||||
<Link
|
||||
href={`/${classSlug}/quizzes/${quiz.id}`}
|
||||
className="flex-1 text-center py-2 px-4 rounded-lg bg-primary text-white text-sm font-medium hover:bg-primary-hover transition-all duration-200"
|
||||
>
|
||||
Start Quiz
|
||||
</Link>
|
||||
<button
|
||||
onClick={() => {
|
||||
setEditingId(quiz.id);
|
||||
setEditName(quiz.name);
|
||||
setEditDescription(quiz.description || "");
|
||||
}}
|
||||
className="p-2 rounded-lg text-text-muted hover:text-text-heading hover:bg-bg-surface-alt transition-all duration-200 cursor-pointer"
|
||||
title="Edit"
|
||||
>
|
||||
<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>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDelete(quiz.id, quiz.name)}
|
||||
className="p-2 rounded-lg text-text-muted hover:text-error hover:bg-error-bg transition-all duration-200 cursor-pointer"
|
||||
title="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>
|
||||
</div>
|
||||
</div>
|
||||
{/* Editing quiz modal/inline - simplified as a modal-like overlay for simplicity when editing */}
|
||||
{editingId && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/40 backdrop-blur-sm">
|
||||
<div className="bg-bg-surface rounded-xl p-6 w-full max-w-md shadow-lg border border-border">
|
||||
<h3 className="text-lg font-semibold text-text-heading mb-4">Edit Quiz</h3>
|
||||
<input
|
||||
type="text"
|
||||
value={editName}
|
||||
onChange={(e) => setEditName(e.target.value)}
|
||||
placeholder="Quiz Name"
|
||||
autoFocus
|
||||
className="w-full px-3 py-2 mb-3 rounded-lg border border-border bg-bg-surface-alt/50 text-text-heading focus:outline-none focus:ring-2 focus:ring-primary/30"
|
||||
/>
|
||||
<textarea
|
||||
value={editDescription}
|
||||
onChange={(e) => setEditDescription(e.target.value)}
|
||||
placeholder="Description (optional)"
|
||||
rows={3}
|
||||
className="w-full px-3 py-2 mb-4 rounded-lg border border-border bg-bg-surface-alt/50 text-text-heading focus:outline-none focus:ring-2 focus:ring-primary/30 resize-none"
|
||||
/>
|
||||
<div className="flex gap-2 justify-end">
|
||||
<button onClick={() => setEditingId(null)} className="px-4 py-2 bg-bg-surface-alt text-text-heading font-medium rounded-md hover:bg-border cursor-pointer">Cancel</button>
|
||||
<button onClick={() => handleRenameQuiz(editingId)} className="px-4 py-2 bg-primary text-white font-medium rounded-md hover:bg-primary-hover cursor-pointer">Save Changes</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Import Modal */}
|
||||
{!loading && quizzes.length === 0 && groups.length === 0 && (
|
||||
<div className="text-center py-16">
|
||||
<h3 className="text-lg font-semibold text-text-heading mb-1">No quizzes yet</h3>
|
||||
<p className="text-text-secondary mb-4">Import your first quiz or create a group</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!loading && (
|
||||
<DndContext sensors={sensors} collisionDetection={pointerWithin} onDragStart={handleDragStart} onDragOver={handleDragOver} onDragEnd={handleDragEnd}>
|
||||
<div className="space-y-8">
|
||||
{groupedQuizzes.map(group => (
|
||||
<div key={group.id} className="bg-bg-surface-alt/30 p-4 rounded-xl border border-border-light">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
{editingGroupId === group.id ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<input autoFocus value={editGroupName} onChange={e => setEditGroupName(e.target.value)} className="px-2 py-1 rounded border border-border bg-bg-surface text-text-heading focus:ring-1 focus:ring-primary" />
|
||||
<button onClick={() => handleRenameGroup(group.id)} className="text-xs px-2 py-1 bg-primary text-white rounded">Save</button>
|
||||
<button onClick={() => setEditingGroupId(null)} className="text-xs px-2 py-1 bg-bg-surface text-text-heading rounded border border-border-light">Cancel</button>
|
||||
</div>
|
||||
) : (
|
||||
<h3 className="text-lg font-semibold text-text-heading">{group.name}</h3>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<ShareMenu targetType="GROUP" contentId={group.id} classSlug={params.classSlug as string} />
|
||||
<button onClick={() => { setEditingGroupId(group.id); setEditGroupName(group.name); }} className="p-1.5 rounded-lg text-text-muted hover:text-text-heading hover:bg-bg-surface transition-colors cursor-pointer"><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></button>
|
||||
<button onClick={() => handleDeleteGroup(group.id, group.name)} className="p-1.5 rounded-lg text-text-muted hover:text-error hover:bg-bg-surface transition-colors cursor-pointer"><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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SortableContext id={group.id} items={group.quizzes.map(q => q.id)} strategy={verticalListSortingStrategy}>
|
||||
<DroppableContainer id={group.id}>
|
||||
{group.quizzes.length === 0 ? (
|
||||
<div className="col-span-1 md:col-span-2 text-center py-4 text-text-muted text-sm border-2 border-dashed border-border-light rounded-lg">Drop quizzes here</div>
|
||||
) : (
|
||||
group.quizzes.map(quiz => (
|
||||
<SortableQuizCard key={quiz.id} quiz={quiz} onEdit={(q: QuizItem) => { setEditingId(q.id); setEditName(q.name); setEditDescription(q.description || ""); }} onDelete={handleDeleteQuiz} classSlug={classSlug} />
|
||||
))
|
||||
)}
|
||||
</DroppableContainer>
|
||||
</SortableContext>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Uncategorized */}
|
||||
<div className="bg-bg-surface-alt/30 p-4 rounded-xl border border-border-light">
|
||||
<h3 className="text-lg font-semibold text-text-heading mb-4">Uncategorized</h3>
|
||||
<SortableContext id="uncategorized" items={uncategorizedQuizzes.map(q => q.id)} strategy={verticalListSortingStrategy}>
|
||||
<DroppableContainer id="uncategorized">
|
||||
{uncategorizedQuizzes.length === 0 ? (
|
||||
<div className="col-span-1 md:col-span-2 text-center py-4 text-text-muted text-sm border-2 border-dashed border-border-light rounded-lg">No uncategorized quizzes</div>
|
||||
) : (
|
||||
uncategorizedQuizzes.map(quiz => (
|
||||
<SortableQuizCard key={quiz.id} quiz={quiz} onEdit={(q: QuizItem) => { setEditingId(q.id); setEditName(q.name); setEditDescription(q.description || ""); }} onDelete={handleDeleteQuiz} classSlug={classSlug} />
|
||||
))
|
||||
)}
|
||||
</DroppableContainer>
|
||||
</SortableContext>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DragOverlay>
|
||||
{activeQuiz ? (
|
||||
<div className="opacity-80 scale-105 shadow-xl rotate-2">
|
||||
<SortableQuizCard quiz={activeQuiz} onEdit={()=>{}} onDelete={()=>{}} classSlug={classSlug} />
|
||||
</div>
|
||||
) : null}
|
||||
</DragOverlay>
|
||||
</DndContext>
|
||||
)}
|
||||
|
||||
{showImport && (
|
||||
<ImportModal
|
||||
classId={classId}
|
||||
importType="quizzes"
|
||||
onClose={() => setShowImport(false)}
|
||||
onImported={() => {
|
||||
setShowImport(false);
|
||||
window.location.reload();
|
||||
}}
|
||||
/>
|
||||
<ImportModal classId={classId} importType="quizzes" onClose={() => setShowImport(false)} onImported={() => { setShowImport(false); window.location.reload(); }} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
31
src/app/api/decks/reorder/route.ts
Normal file
31
src/app/api/decks/reorder/route.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { prisma } from "@/lib/db";
|
||||
|
||||
export async function PATCH(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { items } = body;
|
||||
|
||||
if (!Array.isArray(items)) {
|
||||
return NextResponse.json({ error: "Invalid input" }, { status: 400 });
|
||||
}
|
||||
|
||||
// items should be an array of { id, sortOrder, groupId }
|
||||
// Using a transaction to perform all updates
|
||||
await prisma.$transaction(
|
||||
items.map((item: any) =>
|
||||
prisma.deck.update({
|
||||
where: { id: item.id },
|
||||
data: {
|
||||
sortOrder: item.sortOrder,
|
||||
groupId: item.groupId,
|
||||
},
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: "Failed to reorder decks" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ export async function POST(request: NextRequest) {
|
|||
return NextResponse.json({ error: "Invalid request body" }, { status: 400 });
|
||||
}
|
||||
|
||||
const { classId, data, name } = body;
|
||||
const { classId, data, name, groupId } = body;
|
||||
|
||||
if (!classId || typeof classId !== "string") {
|
||||
return NextResponse.json({ error: "classId is required" }, { status: 400 });
|
||||
|
|
@ -27,7 +27,8 @@ export async function POST(request: NextRequest) {
|
|||
const deck = await deckService.createDeckFromImport(
|
||||
classId,
|
||||
parsed.data,
|
||||
name?.trim() || undefined
|
||||
name?.trim() || undefined,
|
||||
groupId || null
|
||||
);
|
||||
|
||||
return NextResponse.json(deck, { status: 201 });
|
||||
|
|
|
|||
41
src/app/api/material-groups/[id]/route.ts
Normal file
41
src/app/api/material-groups/[id]/route.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { prisma } from "@/lib/db";
|
||||
|
||||
export async function PATCH(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const { id } = await params;
|
||||
const body = await request.json();
|
||||
const { name, sortOrder } = body;
|
||||
|
||||
const updateData: any = {};
|
||||
if (name !== undefined) updateData.name = name;
|
||||
if (sortOrder !== undefined) updateData.sortOrder = sortOrder;
|
||||
|
||||
const group = await prisma.materialGroup.update({
|
||||
where: { id },
|
||||
data: updateData,
|
||||
});
|
||||
|
||||
return NextResponse.json(group);
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: "Failed to update material group" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const { id } = await params;
|
||||
await prisma.materialGroup.delete({
|
||||
where: { id },
|
||||
});
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: "Failed to delete material group" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
56
src/app/api/material-groups/route.ts
Normal file
56
src/app/api/material-groups/route.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { prisma } from "@/lib/db";
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const classId = searchParams.get("classId");
|
||||
const type = searchParams.get("type");
|
||||
|
||||
if (!classId) {
|
||||
return NextResponse.json({ error: "classId is required" }, { status: 400 });
|
||||
}
|
||||
|
||||
if (type && type !== "DECK" && type !== "QUIZ") {
|
||||
return NextResponse.json({ error: "Invalid type" }, { status: 400 });
|
||||
}
|
||||
|
||||
const whereClause: any = { classId };
|
||||
if (type) {
|
||||
whereClause.type = type;
|
||||
}
|
||||
|
||||
try {
|
||||
const groups = await prisma.materialGroup.findMany({
|
||||
where: whereClause,
|
||||
orderBy: { sortOrder: "asc" },
|
||||
});
|
||||
return NextResponse.json(groups);
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: "Failed to fetch material groups" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { classId, name, type } = body;
|
||||
|
||||
if (!classId || !name || (type !== "DECK" && type !== "QUIZ")) {
|
||||
return NextResponse.json({ error: "Invalid input" }, { status: 400 });
|
||||
}
|
||||
|
||||
const maxOrder = await prisma.materialGroup.aggregate({
|
||||
where: { classId, type },
|
||||
_max: { sortOrder: true },
|
||||
});
|
||||
const sortOrder = (maxOrder._max.sortOrder ?? -1) + 1;
|
||||
|
||||
const group = await prisma.materialGroup.create({
|
||||
data: { classId, name, type, sortOrder },
|
||||
});
|
||||
|
||||
return NextResponse.json(group, { status: 201 });
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: "Failed to create material group" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
31
src/app/api/quizzes/reorder/route.ts
Normal file
31
src/app/api/quizzes/reorder/route.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { prisma } from "@/lib/db";
|
||||
|
||||
export async function PATCH(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { items } = body;
|
||||
|
||||
if (!Array.isArray(items)) {
|
||||
return NextResponse.json({ error: "Invalid input" }, { status: 400 });
|
||||
}
|
||||
|
||||
// items should be an array of { id, sortOrder, groupId }
|
||||
// Using a transaction to perform all updates
|
||||
await prisma.$transaction(
|
||||
items.map((item: any) =>
|
||||
prisma.quizSet.update({
|
||||
where: { id: item.id },
|
||||
data: {
|
||||
sortOrder: item.sortOrder,
|
||||
groupId: item.groupId,
|
||||
},
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: "Failed to reorder quizzes" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ export async function POST(request: NextRequest) {
|
|||
return NextResponse.json({ error: "Invalid request body" }, { status: 400 });
|
||||
}
|
||||
|
||||
const { classId, data, name } = body;
|
||||
const { classId, data, name, groupId } = body;
|
||||
|
||||
if (!classId || typeof classId !== "string") {
|
||||
return NextResponse.json({ error: "classId is required" }, { status: 400 });
|
||||
|
|
@ -27,7 +27,8 @@ export async function POST(request: NextRequest) {
|
|||
const quizSet = await quizService.createQuizSetFromImport(
|
||||
classId,
|
||||
parsed.data,
|
||||
name?.trim() || undefined
|
||||
name?.trim() || undefined,
|
||||
groupId || null
|
||||
);
|
||||
|
||||
return NextResponse.json(quizSet, { status: 201 });
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { toggleShareLink, getShareLinkForContent } from "@/services/shareService";
|
||||
import { toggleShareLink, getShareLinkForContent, isContentSharedViaGroup } from "@/services/shareService";
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const targetType = searchParams.get("targetType") as "DECK" | "QUIZ";
|
||||
const targetType = searchParams.get("targetType") as "DECK" | "QUIZ" | "GROUP";
|
||||
const contentId = searchParams.get("contentId");
|
||||
|
||||
if (!targetType || !contentId) {
|
||||
|
|
@ -14,7 +14,19 @@ export async function GET(request: NextRequest) {
|
|||
}
|
||||
|
||||
const link = await getShareLinkForContent(targetType, contentId);
|
||||
return NextResponse.json({ token: link?.id || null });
|
||||
|
||||
if (targetType === "DECK" || targetType === "QUIZ") {
|
||||
const groupShare = await isContentSharedViaGroup(targetType, contentId);
|
||||
if (groupShare) {
|
||||
return NextResponse.json({
|
||||
token: groupShare.token,
|
||||
isGroupShared: true,
|
||||
groupName: groupShare.groupName,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({ token: link?.id || null, isGroupShared: false });
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
|
|
@ -27,6 +39,6 @@ export async function POST(request: NextRequest) {
|
|||
);
|
||||
}
|
||||
|
||||
const link = await toggleShareLink(body.targetType, body.contentId);
|
||||
const link = await toggleShareLink(body.targetType as "DECK" | "QUIZ" | "GROUP", body.contentId);
|
||||
return NextResponse.json({ token: link?.id || null });
|
||||
}
|
||||
|
|
|
|||
133
src/app/shared/[classSlug]/[type]/[token]/SharedGroupViewer.tsx
Normal file
133
src/app/shared/[classSlug]/[type]/[token]/SharedGroupViewer.tsx
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
import Link from "next/link";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
|
||||
export function SharedGroupViewer({ data }: { data: any }) {
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
|
||||
// Combine decks and quizSets if needed, but a group is either DECK or QUIZ type.
|
||||
const items = data.type === "DECK" ? data.decks : data.quizSets;
|
||||
|
||||
const [progressMap, setProgressMap] = useState<Record<string, { currentIndex: number, total: number }>>({});
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
const newProgress: Record<string, { currentIndex: number, total: number }> = {};
|
||||
items.forEach((item: any) => {
|
||||
const key = data.type === "DECK" ? `flashcard_progress_${item.id}` : `quiz_progress_${item.id}`;
|
||||
const saved = localStorage.getItem(key);
|
||||
if (saved) {
|
||||
try {
|
||||
const parsed = JSON.parse(saved);
|
||||
const total = data.type === "DECK" ? (parsed.order?.length || item.cards?.length || 0) : (parsed.order?.length || item.questions?.length || 0);
|
||||
newProgress[item.id] = {
|
||||
currentIndex: parsed.currentIndex || 0,
|
||||
total,
|
||||
};
|
||||
} catch(e) {}
|
||||
}
|
||||
});
|
||||
setProgressMap(newProgress);
|
||||
}, [items, data.type]);
|
||||
|
||||
const handleRestart = (e: React.MouseEvent, itemId: string) => {
|
||||
e.preventDefault();
|
||||
const key = data.type === "DECK" ? `flashcard_progress_${itemId}` : `quiz_progress_${itemId}`;
|
||||
localStorage.removeItem(key);
|
||||
setProgressMap(prev => {
|
||||
const next = { ...prev };
|
||||
delete next[itemId];
|
||||
return next;
|
||||
});
|
||||
router.push(`${pathname}?itemId=${itemId}`);
|
||||
};
|
||||
|
||||
if (!items || items.length === 0) {
|
||||
return (
|
||||
<div className="text-center py-12">
|
||||
<h2 className="text-xl font-medium text-text-muted mb-2">This group is empty</h2>
|
||||
<p className="text-text-secondary">There are no study materials in this group yet.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-8 text-center md:text-left">
|
||||
<h1 className="text-3xl font-bold font-serif text-text-heading mb-2">
|
||||
{data.name}
|
||||
</h1>
|
||||
<p className="text-text-secondary">
|
||||
From class: <span className="font-medium text-text-heading">{data.class.name}</span>
|
||||
</p>
|
||||
<div className="mt-4 inline-block px-3 py-1 bg-primary/10 text-primary rounded-full text-sm font-medium">
|
||||
{data.type === "DECK" ? "Flashcard Group" : "Quiz Group"}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{items.map((item: any) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className="group flex flex-col bg-bg-surface border border-border-light rounded-xl shadow-[var(--shadow-card)] hover:shadow-[var(--shadow-card-hover)] hover:border-primary/20 transition-all duration-300"
|
||||
>
|
||||
<div className="p-4 flex flex-col flex-1 h-full">
|
||||
<div className="flex items-start gap-2">
|
||||
<div className="flex-1">
|
||||
<h3 className="text-lg font-semibold text-text-heading mb-1">{item.name}</h3>
|
||||
{item.description && <p className="text-sm text-text-secondary mb-3 line-clamp-2">{item.description}</p>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 mt-auto pl-1">
|
||||
{progressMap[item.id] ? (
|
||||
<span className="inline-flex items-center gap-1 px-2.5 py-1 rounded-full bg-primary/10 text-primary text-xs font-medium font-bold">
|
||||
{progressMap[item.id].currentIndex} / {progressMap[item.id].total} {data.type === "DECK" ? "cards" : "questions"}
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center gap-1 px-2.5 py-1 rounded-full bg-badge-bg text-badge-text text-xs font-medium">
|
||||
{data.type === "DECK" ? `${item.cards?.length || 0} cards` : `${item.questions?.length || 0} questions`}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 mt-4 pt-4 border-t border-border-light pl-1">
|
||||
{progressMap[item.id] ? (
|
||||
<>
|
||||
<Link
|
||||
href={`${pathname}?itemId=${item.id}`}
|
||||
className="flex-1 text-center py-2 px-4 rounded-lg bg-primary text-white text-sm font-medium hover:bg-primary-hover transition-all duration-200"
|
||||
>
|
||||
Resume
|
||||
</Link>
|
||||
<button
|
||||
onClick={(e) => handleRestart(e, item.id)}
|
||||
className="p-2 rounded-lg text-text-muted hover:text-text-heading hover:bg-bg-surface-alt transition-all duration-200 cursor-pointer border border-border-light"
|
||||
title="Restart"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
</svg>
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<Link
|
||||
href={`${pathname}?itemId=${item.id}`}
|
||||
className="flex-1 text-center py-2 px-4 rounded-lg bg-primary text-white text-sm font-medium hover:bg-primary-hover transition-all duration-200"
|
||||
>
|
||||
Study
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { FlashcardViewer } from "@/components/flashcards/FlashcardViewer";
|
||||
import { QuizViewer } from "@/components/quizzes/QuizViewer";
|
||||
import { CardList } from "@/components/flashcards/CardList";
|
||||
|
|
@ -8,11 +10,29 @@ import { CardList } from "@/components/flashcards/CardList";
|
|||
interface SharedViewerProps {
|
||||
type: string;
|
||||
data: any;
|
||||
groupMode?: boolean;
|
||||
}
|
||||
|
||||
export function SharedViewer({ type, data }: SharedViewerProps) {
|
||||
export function SharedViewer({ type, data, groupMode = false }: SharedViewerProps) {
|
||||
const pathname = usePathname();
|
||||
const [restartKey, setRestartKey] = useState(0);
|
||||
const [view, setView] = useState<"study" | "list">("study");
|
||||
const [hasSavedSession, setHasSavedSession] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Check if there's a saved session for this item
|
||||
const key = type === "flashcards" ? `flashcard_progress_${data.id}` : `quiz_progress_${data.id}`;
|
||||
if (localStorage.getItem(key)) {
|
||||
setHasSavedSession(true);
|
||||
}
|
||||
}, [type, data.id, restartKey]);
|
||||
|
||||
function handleRestart() {
|
||||
const key = type === "flashcards" ? `flashcard_progress_${data.id}` : `quiz_progress_${data.id}`;
|
||||
localStorage.removeItem(key);
|
||||
setHasSavedSession(false);
|
||||
setRestartKey(k => k + 1);
|
||||
}
|
||||
|
||||
const [showTopics, setShowTopics] = useState(false);
|
||||
const categories = type === "quizzes" && data.questions
|
||||
|
|
@ -21,6 +41,14 @@ export function SharedViewer({ type, data }: SharedViewerProps) {
|
|||
|
||||
return (
|
||||
<div>
|
||||
{groupMode && (
|
||||
<div className="mb-6">
|
||||
<Link href={pathname} className="inline-flex items-center gap-2 text-sm font-medium text-text-muted hover:text-primary transition-colors">
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 19l-7-7m0 0l7-7m-7 7h18" /></svg>
|
||||
Back to Group
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-4 mb-8">
|
||||
<div className="text-left">
|
||||
{type === "quizzes" ? (
|
||||
|
|
@ -63,15 +91,16 @@ export function SharedViewer({ type, data }: SharedViewerProps) {
|
|||
</div>
|
||||
|
||||
<div className="flex items-center justify-between w-full md:w-auto gap-2 md:gap-4 self-end md:self-auto">
|
||||
{view === "study" && (
|
||||
{view === "study" && hasSavedSession && (
|
||||
<button
|
||||
onClick={() => setRestartKey(k => k + 1)}
|
||||
className="p-1.5 rounded-lg text-text-muted hover:text-text-heading hover:bg-bg-surface-alt transition-all duration-200 cursor-pointer"
|
||||
onClick={handleRestart}
|
||||
className="p-1.5 rounded-lg text-text-muted hover:text-text-heading hover:bg-bg-surface-alt transition-all duration-200 cursor-pointer flex items-center gap-2 font-medium text-sm"
|
||||
title="Restart Session"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
</svg>
|
||||
<span className="hidden md:inline">Restart</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { notFound } from "next/navigation";
|
|||
import { ThemeToggle } from "@/components/ui/ThemeToggle";
|
||||
import Link from "next/link";
|
||||
import { SharedViewer } from "./SharedViewer";
|
||||
import { SharedGroupViewer } from "./SharedGroupViewer";
|
||||
|
||||
interface SharedPageProps {
|
||||
params: Promise<{
|
||||
|
|
@ -12,10 +13,11 @@ interface SharedPageProps {
|
|||
}>;
|
||||
}
|
||||
|
||||
export default async function SharedPage(props: SharedPageProps) {
|
||||
export default async function SharedPage(props: SharedPageProps & { searchParams: Promise<{ itemId?: string }> }) {
|
||||
const { classSlug, type, token } = await props.params;
|
||||
const { itemId } = await props.searchParams;
|
||||
|
||||
if (type !== "flashcards" && type !== "quizzes") {
|
||||
if (type !== "flashcards" && type !== "quizzes" && type !== "groups") {
|
||||
notFound();
|
||||
}
|
||||
|
||||
|
|
@ -28,12 +30,34 @@ export default async function SharedPage(props: SharedPageProps) {
|
|||
// Validate that the link matches the URL structure
|
||||
if (type === "flashcards" && !link.deck) notFound();
|
||||
if (type === "quizzes" && !link.quizSet) notFound();
|
||||
if (type === "groups" && !link.group) notFound();
|
||||
|
||||
// Validate class slug matches
|
||||
const targetClassSlug = link.deck ? link.deck.class.slug : link.quizSet?.class.slug;
|
||||
const targetClassSlug = link.deck ? link.deck.class.slug : (link.quizSet ? link.quizSet.class.slug : link.group?.class.slug);
|
||||
if (targetClassSlug !== classSlug) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
let targetData: any = null;
|
||||
let targetType = type;
|
||||
|
||||
if (type === "groups" && link.group) {
|
||||
if (itemId) {
|
||||
// Find the specific item inside the group
|
||||
if (link.group.type === "DECK") {
|
||||
targetData = link.group.decks.find(d => d.id === itemId);
|
||||
targetType = "flashcards";
|
||||
} else {
|
||||
targetData = link.group.quizSets.find(q => q.id === itemId);
|
||||
targetType = "quizzes";
|
||||
}
|
||||
if (!targetData) notFound();
|
||||
} else {
|
||||
targetData = link.group;
|
||||
}
|
||||
} else {
|
||||
targetData = type === "flashcards" ? link.deck : link.quizSet;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-bg-base flex flex-col">
|
||||
|
|
@ -45,7 +69,7 @@ export default async function SharedPage(props: SharedPageProps) {
|
|||
<svg className="w-5 h-5 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" />
|
||||
</svg>
|
||||
Shared {type === "flashcards" ? "Deck" : "Quiz"}
|
||||
Shared {type === "groups" ? "Group" : (type === "flashcards" ? "Deck" : "Quiz")}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
|
|
@ -69,10 +93,15 @@ export default async function SharedPage(props: SharedPageProps) {
|
|||
{/* Main Content */}
|
||||
<main className="flex-1 overflow-y-auto">
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<SharedViewer
|
||||
type={type}
|
||||
data={type === "flashcards" ? link.deck : link.quizSet}
|
||||
/>
|
||||
{type === "groups" && !itemId ? (
|
||||
<SharedGroupViewer data={targetData} />
|
||||
) : (
|
||||
<SharedViewer
|
||||
type={targetType}
|
||||
data={targetData}
|
||||
groupMode={type === "groups"}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ interface FlashcardViewerProps {
|
|||
type CardResult = "correct" | "missed";
|
||||
|
||||
export function FlashcardViewer({ cards, deckId, isShared = false, initialProgress }: FlashcardViewerProps) {
|
||||
const [isLoaded, setIsLoaded] = useState(!isShared);
|
||||
|
||||
const [order, setOrder] = useState<string[]>(
|
||||
initialProgress ? JSON.parse(initialProgress.orderJson) : cards.map((c) => c.id)
|
||||
);
|
||||
|
|
@ -70,6 +72,30 @@ export function FlashcardViewer({ cards, deckId, isShared = false, initialProgre
|
|||
}
|
||||
}, [toastMessage]);
|
||||
|
||||
// Load from localStorage if shared
|
||||
useEffect(() => {
|
||||
if (isShared) {
|
||||
try {
|
||||
const saved = localStorage.getItem(`flashcard_progress_${deckId}`);
|
||||
if (saved) {
|
||||
const parsed = JSON.parse(saved);
|
||||
setOrder(parsed.order || cards.map(c => c.id));
|
||||
setCurrentIndex(parsed.currentIndex || 0);
|
||||
setResults(parsed.results || {});
|
||||
setIsShuffled(parsed.mode === "SHUFFLED");
|
||||
|
||||
if (parsed.currentIndex >= (parsed.order?.length || cards.length)) {
|
||||
setCompleted(true);
|
||||
}
|
||||
setToastMessage("Session restored");
|
||||
}
|
||||
} catch (e) {
|
||||
// Fallback to defaults
|
||||
}
|
||||
setIsLoaded(true);
|
||||
}
|
||||
}, [isShared, deckId, cards]);
|
||||
|
||||
function toggleShuffle() {
|
||||
const newShuffled = !isShuffled;
|
||||
setIsShuffled(newShuffled);
|
||||
|
|
@ -184,7 +210,15 @@ export function FlashcardViewer({ cards, deckId, isShared = false, initialProgre
|
|||
cardResults: Record<string, CardResult>,
|
||||
m: "SEQUENTIAL" | "SHUFFLED"
|
||||
) {
|
||||
if (isShared) return;
|
||||
if (isShared) {
|
||||
localStorage.setItem(`flashcard_progress_${deckId}`, JSON.stringify({
|
||||
mode: m,
|
||||
currentIndex: index,
|
||||
order: orderArr,
|
||||
results: cardResults,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
fetch("/api/progress", {
|
||||
method: "PATCH",
|
||||
|
|
@ -238,6 +272,8 @@ export function FlashcardViewer({ cards, deckId, isShared = false, initialProgre
|
|||
}
|
||||
|
||||
// Completed summary
|
||||
if (!isLoaded) return <div className="text-center py-12 text-text-muted">Loading session...</div>;
|
||||
|
||||
if (completed) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center py-16">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { parseAndRepairJson } from "@/lib/jsonRepair";
|
||||
import {
|
||||
flashcardImportSchema,
|
||||
|
|
@ -23,12 +23,35 @@ interface PreviewData {
|
|||
data: unknown;
|
||||
}
|
||||
|
||||
interface MaterialGroup {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export function ImportTab({ classId, importType, onImported }: ImportTabProps) {
|
||||
const [jsonText, setJsonText] = useState("");
|
||||
const [name, setName] = useState("");
|
||||
const [preview, setPreview] = useState<PreviewData | null>(null);
|
||||
const [error, setError] = useState("");
|
||||
const [importing, setImporting] = useState(false);
|
||||
const [materialGroups, setMaterialGroups] = useState<MaterialGroup[]>([]);
|
||||
const [selectedGroupId, setSelectedGroupId] = useState<string>("");
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchGroups() {
|
||||
try {
|
||||
const typeParam = importType === "flashcards" ? "DECK" : "QUIZ";
|
||||
const res = await fetch(`/api/material-groups?classId=${classId}&type=${typeParam}`);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
setMaterialGroups(data);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to fetch material groups", err);
|
||||
}
|
||||
}
|
||||
fetchGroups();
|
||||
}, [classId, importType]);
|
||||
|
||||
function handleJsonChange(text: string) {
|
||||
setJsonText(text);
|
||||
|
|
@ -101,6 +124,7 @@ export function ImportTab({ classId, importType, onImported }: ImportTabProps) {
|
|||
classId,
|
||||
data: preview.data,
|
||||
name: name.trim(),
|
||||
groupId: selectedGroupId || null,
|
||||
}),
|
||||
});
|
||||
|
||||
|
|
@ -136,6 +160,25 @@ export function ImportTab({ classId, importType, onImported }: ImportTabProps) {
|
|||
/>
|
||||
</div>
|
||||
|
||||
{/* Group field */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-text-secondary mb-1.5">
|
||||
Group (Optional)
|
||||
</label>
|
||||
<select
|
||||
value={selectedGroupId}
|
||||
onChange={(e) => setSelectedGroupId(e.target.value)}
|
||||
className="w-full px-3.5 py-2.5 rounded-lg border border-border bg-bg-surface-alt/50 text-text-heading focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary transition-all duration-200"
|
||||
>
|
||||
<option value="">Uncategorized</option>
|
||||
{materialGroups.map((group) => (
|
||||
<option key={group.id} value={group.id}>
|
||||
{group.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* JSON paste area */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-text-secondary mb-1.5">
|
||||
|
|
|
|||
|
|
@ -47,6 +47,15 @@ export function QuizViewer({ quiz, retakeIds, isShared = false, onFinished }: Qu
|
|||
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [resultsData, setResultsData] = useState<any | null>(null);
|
||||
const [toastMessage, setToastMessage] = useState<string | null>(null);
|
||||
|
||||
// Auto-hide toast
|
||||
useEffect(() => {
|
||||
if (toastMessage) {
|
||||
const timer = setTimeout(() => setToastMessage(null), 3000);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [toastMessage]);
|
||||
|
||||
// Initialize session
|
||||
useEffect(() => {
|
||||
|
|
@ -67,6 +76,25 @@ export function QuizViewer({ quiz, retakeIds, isShared = false, onFinished }: Qu
|
|||
return;
|
||||
}
|
||||
|
||||
// Shared links: load from localStorage
|
||||
if (isShared) {
|
||||
try {
|
||||
const saved = localStorage.getItem(`quiz_progress_${quiz.id}`);
|
||||
if (saved) {
|
||||
const parsed = JSON.parse(saved);
|
||||
setOrder(parsed.order || quiz.questions.map(q => q.id));
|
||||
setCurrentIndex(parsed.currentIndex || 0);
|
||||
setAnswers(parsed.answers || {});
|
||||
setSubmittedAnswers(Object.keys(parsed.answers || {}));
|
||||
setToastMessage("Session restored");
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
// Fallback to defaults
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, normal sequential logic checking for progress
|
||||
const prog = quiz.progress?.find((p) => p.mode === "SEQUENTIAL");
|
||||
if (prog) {
|
||||
|
|
@ -96,14 +124,23 @@ export function QuizViewer({ quiz, retakeIds, isShared = false, onFinished }: Qu
|
|||
setSubmittedAnswers([]);
|
||||
}
|
||||
setLoading(false);
|
||||
}, [quiz, retakeIds]);
|
||||
}, [quiz, retakeIds, isShared]);
|
||||
|
||||
// Persist progress as you go
|
||||
function saveProgress(
|
||||
idx: number,
|
||||
ans: Record<string, string[]>
|
||||
) {
|
||||
if (retakeIds || isShared) return; // Don't persist retake partial state or shared mode into the main progress row
|
||||
if (retakeIds) return; // Don't persist retake partial state
|
||||
|
||||
if (isShared) {
|
||||
localStorage.setItem(`quiz_progress_${quiz.id}`, JSON.stringify({
|
||||
currentIndex: idx,
|
||||
order: order,
|
||||
answers: ans,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
fetch("/api/progress", {
|
||||
method: "PATCH",
|
||||
|
|
@ -172,6 +209,10 @@ export function QuizViewer({ quiz, retakeIds, isShared = false, onFinished }: Qu
|
|||
}
|
||||
|
||||
async function handleFinish() {
|
||||
if (isShared) {
|
||||
localStorage.removeItem(`quiz_progress_${quiz.id}`);
|
||||
}
|
||||
|
||||
// If shared, grade it locally and show results, don't ping backend
|
||||
if (isShared) {
|
||||
// Simulate an attempt object
|
||||
|
|
@ -268,10 +309,18 @@ export function QuizViewer({ quiz, retakeIds, isShared = false, onFinished }: Qu
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="flex flex-col items-center relative">
|
||||
{/* Toast Notification */}
|
||||
{toastMessage && (
|
||||
<div className="absolute top-4 left-1/2 -translate-x-1/2 z-30 animate-fade-in pointer-events-none">
|
||||
<div className="bg-bg-surface-alt/95 backdrop-blur shadow-[var(--shadow-card)] text-text-heading text-sm font-semibold px-6 py-2.5 rounded-full whitespace-nowrap">
|
||||
{toastMessage}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Main Unified Card */}
|
||||
<div className="w-full max-w-3xl bg-bg-surface-alt rounded-2xl border border-border-light shadow-[var(--shadow-card)] overflow-hidden mb-8">
|
||||
<div className="w-full max-w-3xl bg-bg-surface-alt rounded-2xl border border-border-light shadow-[var(--shadow-card)] overflow-hidden mb-8 mt-12">
|
||||
|
||||
{/* Progress Header */}
|
||||
<div className="px-6 md:px-8 py-5 flex items-center gap-4">
|
||||
|
|
@ -434,7 +483,7 @@ export function QuizViewer({ quiz, retakeIds, isShared = false, onFinished }: Qu
|
|||
<button
|
||||
onClick={handleSubmit}
|
||||
disabled={currentSelections.length === 0}
|
||||
className="w-full py-3.5 rounded-xl bg-primary/60 hover:bg-primary/80 text-white font-bold text-lg tracking-wide disabled:opacity-50 disabled:hover:bg-primary/60 transition-all duration-200 cursor-pointer shadow-sm"
|
||||
className="w-full py-3.5 rounded-xl bg-primary hover:bg-primary-hover text-white font-bold text-lg tracking-wide disabled:opacity-50 disabled:bg-primary/50 disabled:hover:bg-primary/50 disabled:cursor-not-allowed transition-all duration-200 shadow-sm"
|
||||
>
|
||||
Submit Answer
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import { useState, useEffect } from "react";
|
||||
|
||||
interface ShareMenuProps {
|
||||
targetType: "DECK" | "QUIZ";
|
||||
targetType: "DECK" | "QUIZ" | "GROUP";
|
||||
contentId: string;
|
||||
classSlug: string;
|
||||
}
|
||||
|
|
@ -13,12 +13,18 @@ export function ShareMenu({ targetType, contentId, classSlug }: ShareMenuProps)
|
|||
const [loading, setLoading] = useState(true);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [isGroupShared, setIsGroupShared] = useState(false);
|
||||
const [groupName, setGroupName] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
fetch(`/api/share?targetType=${targetType}&contentId=${contentId}`)
|
||||
.then(res => res.json())
|
||||
.then(data => setToken(data.token))
|
||||
.then(data => {
|
||||
setToken(data.token);
|
||||
setIsGroupShared(data.isGroupShared || false);
|
||||
setGroupName(data.groupName || null);
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
}, [open, targetType, contentId]);
|
||||
|
||||
|
|
@ -39,7 +45,7 @@ export function ShareMenu({ targetType, contentId, classSlug }: ShareMenuProps)
|
|||
|
||||
async function handleCopy() {
|
||||
if (!token) return;
|
||||
const typeStr = targetType === "DECK" ? "flashcards" : "quizzes";
|
||||
const typeStr = targetType === "GROUP" ? "groups" : (targetType === "DECK" ? "flashcards" : "quizzes");
|
||||
const url = `${window.location.origin}/shared/${classSlug}/${typeStr}/${token}`;
|
||||
await navigator.clipboard.writeText(url);
|
||||
setCopied(true);
|
||||
|
|
@ -72,22 +78,29 @@ export function ShareMenu({ targetType, contentId, classSlug }: ShareMenuProps)
|
|||
<span className="text-sm text-text-secondary">Enable link sharing</span>
|
||||
<button
|
||||
onClick={handleToggle}
|
||||
disabled={isGroupShared}
|
||||
className={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors ${
|
||||
token ? 'bg-primary' : 'bg-border'
|
||||
}`}
|
||||
token || isGroupShared ? 'bg-primary' : 'bg-border'
|
||||
} ${isGroupShared ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}`}
|
||||
>
|
||||
<span
|
||||
className={`inline-block h-3 w-3 transform rounded-full bg-white transition-transform ${
|
||||
token ? 'translate-x-5' : 'translate-x-1'
|
||||
token || isGroupShared ? 'translate-x-5' : 'translate-x-1'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{isGroupShared && (
|
||||
<div className="text-xs text-primary font-medium px-3 py-2 bg-primary/10 rounded-lg">
|
||||
This item is publicly shared via its group: <span className="font-bold">{groupName}</span>. Turn off group sharing to restrict access.
|
||||
</div>
|
||||
)}
|
||||
|
||||
{token && (
|
||||
{(token || isGroupShared) && (
|
||||
<div className="space-y-2 pt-2 border-t border-border-light">
|
||||
<p className="text-xs text-text-muted">
|
||||
Anyone with the link can view and study this {targetType === "DECK" ? "deck" : "quiz"}. No progress is saved.
|
||||
Anyone with the link can view and study this {targetType === "GROUP" ? "group" : (targetType === "DECK" ? "deck" : "quiz")}. No progress is saved.
|
||||
</p>
|
||||
<button
|
||||
onClick={handleCopy}
|
||||
|
|
|
|||
|
|
@ -72,3 +72,8 @@ export type AuthSecurity = Prisma.AuthSecurityModel
|
|||
*
|
||||
*/
|
||||
export type Setting = Prisma.SettingModel
|
||||
/**
|
||||
* Model MaterialGroup
|
||||
*
|
||||
*/
|
||||
export type MaterialGroup = Prisma.MaterialGroupModel
|
||||
|
|
|
|||
|
|
@ -96,3 +96,8 @@ export type AuthSecurity = Prisma.AuthSecurityModel
|
|||
*
|
||||
*/
|
||||
export type Setting = Prisma.SettingModel
|
||||
/**
|
||||
* Model MaterialGroup
|
||||
*
|
||||
*/
|
||||
export type MaterialGroup = Prisma.MaterialGroupModel
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -394,7 +394,8 @@ export const ModelName = {
|
|||
QuizAttempt: 'QuizAttempt',
|
||||
ShareLink: 'ShareLink',
|
||||
AuthSecurity: 'AuthSecurity',
|
||||
Setting: 'Setting'
|
||||
Setting: 'Setting',
|
||||
MaterialGroup: 'MaterialGroup'
|
||||
} as const
|
||||
|
||||
export type ModelName = (typeof ModelName)[keyof typeof ModelName]
|
||||
|
|
@ -410,7 +411,7 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
|
|||
omit: GlobalOmitOptions
|
||||
}
|
||||
meta: {
|
||||
modelProps: "class" | "deck" | "flashcard" | "quizSet" | "question" | "answerOption" | "studyProgress" | "quizAttempt" | "shareLink" | "authSecurity" | "setting"
|
||||
modelProps: "class" | "deck" | "flashcard" | "quizSet" | "question" | "answerOption" | "studyProgress" | "quizAttempt" | "shareLink" | "authSecurity" | "setting" | "materialGroup"
|
||||
txIsolationLevel: TransactionIsolationLevel
|
||||
}
|
||||
model: {
|
||||
|
|
@ -1228,6 +1229,80 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
|
|||
}
|
||||
}
|
||||
}
|
||||
MaterialGroup: {
|
||||
payload: Prisma.$MaterialGroupPayload<ExtArgs>
|
||||
fields: Prisma.MaterialGroupFieldRefs
|
||||
operations: {
|
||||
findUnique: {
|
||||
args: Prisma.MaterialGroupFindUniqueArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$MaterialGroupPayload> | null
|
||||
}
|
||||
findUniqueOrThrow: {
|
||||
args: Prisma.MaterialGroupFindUniqueOrThrowArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$MaterialGroupPayload>
|
||||
}
|
||||
findFirst: {
|
||||
args: Prisma.MaterialGroupFindFirstArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$MaterialGroupPayload> | null
|
||||
}
|
||||
findFirstOrThrow: {
|
||||
args: Prisma.MaterialGroupFindFirstOrThrowArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$MaterialGroupPayload>
|
||||
}
|
||||
findMany: {
|
||||
args: Prisma.MaterialGroupFindManyArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$MaterialGroupPayload>[]
|
||||
}
|
||||
create: {
|
||||
args: Prisma.MaterialGroupCreateArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$MaterialGroupPayload>
|
||||
}
|
||||
createMany: {
|
||||
args: Prisma.MaterialGroupCreateManyArgs<ExtArgs>
|
||||
result: BatchPayload
|
||||
}
|
||||
createManyAndReturn: {
|
||||
args: Prisma.MaterialGroupCreateManyAndReturnArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$MaterialGroupPayload>[]
|
||||
}
|
||||
delete: {
|
||||
args: Prisma.MaterialGroupDeleteArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$MaterialGroupPayload>
|
||||
}
|
||||
update: {
|
||||
args: Prisma.MaterialGroupUpdateArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$MaterialGroupPayload>
|
||||
}
|
||||
deleteMany: {
|
||||
args: Prisma.MaterialGroupDeleteManyArgs<ExtArgs>
|
||||
result: BatchPayload
|
||||
}
|
||||
updateMany: {
|
||||
args: Prisma.MaterialGroupUpdateManyArgs<ExtArgs>
|
||||
result: BatchPayload
|
||||
}
|
||||
updateManyAndReturn: {
|
||||
args: Prisma.MaterialGroupUpdateManyAndReturnArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$MaterialGroupPayload>[]
|
||||
}
|
||||
upsert: {
|
||||
args: Prisma.MaterialGroupUpsertArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$MaterialGroupPayload>
|
||||
}
|
||||
aggregate: {
|
||||
args: Prisma.MaterialGroupAggregateArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.Optional<Prisma.AggregateMaterialGroup>
|
||||
}
|
||||
groupBy: {
|
||||
args: Prisma.MaterialGroupGroupByArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.Optional<Prisma.MaterialGroupGroupByOutputType>[]
|
||||
}
|
||||
count: {
|
||||
args: Prisma.MaterialGroupCountArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.Optional<Prisma.MaterialGroupCountAggregateOutputType> | number
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} & {
|
||||
other: {
|
||||
|
|
@ -1281,7 +1356,8 @@ export const DeckScalarFieldEnum = {
|
|||
name: 'name',
|
||||
description: 'description',
|
||||
sortOrder: 'sortOrder',
|
||||
createdAt: 'createdAt'
|
||||
createdAt: 'createdAt',
|
||||
groupId: 'groupId'
|
||||
} as const
|
||||
|
||||
export type DeckScalarFieldEnum = (typeof DeckScalarFieldEnum)[keyof typeof DeckScalarFieldEnum]
|
||||
|
|
@ -1304,7 +1380,8 @@ export const QuizSetScalarFieldEnum = {
|
|||
name: 'name',
|
||||
description: 'description',
|
||||
sortOrder: 'sortOrder',
|
||||
createdAt: 'createdAt'
|
||||
createdAt: 'createdAt',
|
||||
groupId: 'groupId'
|
||||
} as const
|
||||
|
||||
export type QuizSetScalarFieldEnum = (typeof QuizSetScalarFieldEnum)[keyof typeof QuizSetScalarFieldEnum]
|
||||
|
|
@ -1368,6 +1445,7 @@ export const ShareLinkScalarFieldEnum = {
|
|||
targetType: 'targetType',
|
||||
deckId: 'deckId',
|
||||
quizSetId: 'quizSetId',
|
||||
groupId: 'groupId',
|
||||
createdAt: 'createdAt'
|
||||
} as const
|
||||
|
||||
|
|
@ -1392,6 +1470,18 @@ export const SettingScalarFieldEnum = {
|
|||
export type SettingScalarFieldEnum = (typeof SettingScalarFieldEnum)[keyof typeof SettingScalarFieldEnum]
|
||||
|
||||
|
||||
export const MaterialGroupScalarFieldEnum = {
|
||||
id: 'id',
|
||||
classId: 'classId',
|
||||
name: 'name',
|
||||
type: 'type',
|
||||
sortOrder: 'sortOrder',
|
||||
createdAt: 'createdAt'
|
||||
} as const
|
||||
|
||||
export type MaterialGroupScalarFieldEnum = (typeof MaterialGroupScalarFieldEnum)[keyof typeof MaterialGroupScalarFieldEnum]
|
||||
|
||||
|
||||
export const SortOrder = {
|
||||
asc: 'asc',
|
||||
desc: 'desc'
|
||||
|
|
@ -1569,6 +1659,7 @@ export type GlobalOmitConfig = {
|
|||
shareLink?: Prisma.ShareLinkOmit
|
||||
authSecurity?: Prisma.AuthSecurityOmit
|
||||
setting?: Prisma.SettingOmit
|
||||
materialGroup?: Prisma.MaterialGroupOmit
|
||||
}
|
||||
|
||||
/* Types for Logging */
|
||||
|
|
|
|||
|
|
@ -61,7 +61,8 @@ export const ModelName = {
|
|||
QuizAttempt: 'QuizAttempt',
|
||||
ShareLink: 'ShareLink',
|
||||
AuthSecurity: 'AuthSecurity',
|
||||
Setting: 'Setting'
|
||||
Setting: 'Setting',
|
||||
MaterialGroup: 'MaterialGroup'
|
||||
} as const
|
||||
|
||||
export type ModelName = (typeof ModelName)[keyof typeof ModelName]
|
||||
|
|
@ -94,7 +95,8 @@ export const DeckScalarFieldEnum = {
|
|||
name: 'name',
|
||||
description: 'description',
|
||||
sortOrder: 'sortOrder',
|
||||
createdAt: 'createdAt'
|
||||
createdAt: 'createdAt',
|
||||
groupId: 'groupId'
|
||||
} as const
|
||||
|
||||
export type DeckScalarFieldEnum = (typeof DeckScalarFieldEnum)[keyof typeof DeckScalarFieldEnum]
|
||||
|
|
@ -117,7 +119,8 @@ export const QuizSetScalarFieldEnum = {
|
|||
name: 'name',
|
||||
description: 'description',
|
||||
sortOrder: 'sortOrder',
|
||||
createdAt: 'createdAt'
|
||||
createdAt: 'createdAt',
|
||||
groupId: 'groupId'
|
||||
} as const
|
||||
|
||||
export type QuizSetScalarFieldEnum = (typeof QuizSetScalarFieldEnum)[keyof typeof QuizSetScalarFieldEnum]
|
||||
|
|
@ -181,6 +184,7 @@ export const ShareLinkScalarFieldEnum = {
|
|||
targetType: 'targetType',
|
||||
deckId: 'deckId',
|
||||
quizSetId: 'quizSetId',
|
||||
groupId: 'groupId',
|
||||
createdAt: 'createdAt'
|
||||
} as const
|
||||
|
||||
|
|
@ -205,6 +209,18 @@ export const SettingScalarFieldEnum = {
|
|||
export type SettingScalarFieldEnum = (typeof SettingScalarFieldEnum)[keyof typeof SettingScalarFieldEnum]
|
||||
|
||||
|
||||
export const MaterialGroupScalarFieldEnum = {
|
||||
id: 'id',
|
||||
classId: 'classId',
|
||||
name: 'name',
|
||||
type: 'type',
|
||||
sortOrder: 'sortOrder',
|
||||
createdAt: 'createdAt'
|
||||
} as const
|
||||
|
||||
export type MaterialGroupScalarFieldEnum = (typeof MaterialGroupScalarFieldEnum)[keyof typeof MaterialGroupScalarFieldEnum]
|
||||
|
||||
|
||||
export const SortOrder = {
|
||||
asc: 'asc',
|
||||
desc: 'desc'
|
||||
|
|
|
|||
|
|
@ -19,4 +19,5 @@ export type * from './models/QuizAttempt'
|
|||
export type * from './models/ShareLink'
|
||||
export type * from './models/AuthSecurity'
|
||||
export type * from './models/Setting'
|
||||
export type * from './models/MaterialGroup'
|
||||
export type * from './commonInputTypes'
|
||||
|
|
@ -218,6 +218,7 @@ export type ClassWhereInput = {
|
|||
createdAt?: Prisma.DateTimeFilter<"Class"> | Date | string
|
||||
decks?: Prisma.DeckListRelationFilter
|
||||
quizSets?: Prisma.QuizSetListRelationFilter
|
||||
materialGroups?: Prisma.MaterialGroupListRelationFilter
|
||||
}
|
||||
|
||||
export type ClassOrderByWithRelationInput = {
|
||||
|
|
@ -228,6 +229,7 @@ export type ClassOrderByWithRelationInput = {
|
|||
createdAt?: Prisma.SortOrder
|
||||
decks?: Prisma.DeckOrderByRelationAggregateInput
|
||||
quizSets?: Prisma.QuizSetOrderByRelationAggregateInput
|
||||
materialGroups?: Prisma.MaterialGroupOrderByRelationAggregateInput
|
||||
}
|
||||
|
||||
export type ClassWhereUniqueInput = Prisma.AtLeast<{
|
||||
|
|
@ -241,6 +243,7 @@ export type ClassWhereUniqueInput = Prisma.AtLeast<{
|
|||
createdAt?: Prisma.DateTimeFilter<"Class"> | Date | string
|
||||
decks?: Prisma.DeckListRelationFilter
|
||||
quizSets?: Prisma.QuizSetListRelationFilter
|
||||
materialGroups?: Prisma.MaterialGroupListRelationFilter
|
||||
}, "id" | "slug">
|
||||
|
||||
export type ClassOrderByWithAggregationInput = {
|
||||
|
|
@ -275,6 +278,7 @@ export type ClassCreateInput = {
|
|||
createdAt?: Date | string
|
||||
decks?: Prisma.DeckCreateNestedManyWithoutClassInput
|
||||
quizSets?: Prisma.QuizSetCreateNestedManyWithoutClassInput
|
||||
materialGroups?: Prisma.MaterialGroupCreateNestedManyWithoutClassInput
|
||||
}
|
||||
|
||||
export type ClassUncheckedCreateInput = {
|
||||
|
|
@ -285,6 +289,7 @@ export type ClassUncheckedCreateInput = {
|
|||
createdAt?: Date | string
|
||||
decks?: Prisma.DeckUncheckedCreateNestedManyWithoutClassInput
|
||||
quizSets?: Prisma.QuizSetUncheckedCreateNestedManyWithoutClassInput
|
||||
materialGroups?: Prisma.MaterialGroupUncheckedCreateNestedManyWithoutClassInput
|
||||
}
|
||||
|
||||
export type ClassUpdateInput = {
|
||||
|
|
@ -295,6 +300,7 @@ export type ClassUpdateInput = {
|
|||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
decks?: Prisma.DeckUpdateManyWithoutClassNestedInput
|
||||
quizSets?: Prisma.QuizSetUpdateManyWithoutClassNestedInput
|
||||
materialGroups?: Prisma.MaterialGroupUpdateManyWithoutClassNestedInput
|
||||
}
|
||||
|
||||
export type ClassUncheckedUpdateInput = {
|
||||
|
|
@ -305,6 +311,7 @@ export type ClassUncheckedUpdateInput = {
|
|||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
decks?: Prisma.DeckUncheckedUpdateManyWithoutClassNestedInput
|
||||
quizSets?: Prisma.QuizSetUncheckedUpdateManyWithoutClassNestedInput
|
||||
materialGroups?: Prisma.MaterialGroupUncheckedUpdateManyWithoutClassNestedInput
|
||||
}
|
||||
|
||||
export type ClassCreateManyInput = {
|
||||
|
|
@ -412,6 +419,20 @@ export type ClassUpdateOneRequiredWithoutQuizSetsNestedInput = {
|
|||
update?: Prisma.XOR<Prisma.XOR<Prisma.ClassUpdateToOneWithWhereWithoutQuizSetsInput, Prisma.ClassUpdateWithoutQuizSetsInput>, Prisma.ClassUncheckedUpdateWithoutQuizSetsInput>
|
||||
}
|
||||
|
||||
export type ClassCreateNestedOneWithoutMaterialGroupsInput = {
|
||||
create?: Prisma.XOR<Prisma.ClassCreateWithoutMaterialGroupsInput, Prisma.ClassUncheckedCreateWithoutMaterialGroupsInput>
|
||||
connectOrCreate?: Prisma.ClassCreateOrConnectWithoutMaterialGroupsInput
|
||||
connect?: Prisma.ClassWhereUniqueInput
|
||||
}
|
||||
|
||||
export type ClassUpdateOneRequiredWithoutMaterialGroupsNestedInput = {
|
||||
create?: Prisma.XOR<Prisma.ClassCreateWithoutMaterialGroupsInput, Prisma.ClassUncheckedCreateWithoutMaterialGroupsInput>
|
||||
connectOrCreate?: Prisma.ClassCreateOrConnectWithoutMaterialGroupsInput
|
||||
upsert?: Prisma.ClassUpsertWithoutMaterialGroupsInput
|
||||
connect?: Prisma.ClassWhereUniqueInput
|
||||
update?: Prisma.XOR<Prisma.XOR<Prisma.ClassUpdateToOneWithWhereWithoutMaterialGroupsInput, Prisma.ClassUpdateWithoutMaterialGroupsInput>, Prisma.ClassUncheckedUpdateWithoutMaterialGroupsInput>
|
||||
}
|
||||
|
||||
export type ClassCreateWithoutDecksInput = {
|
||||
id?: string
|
||||
slug: string
|
||||
|
|
@ -419,6 +440,7 @@ export type ClassCreateWithoutDecksInput = {
|
|||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
quizSets?: Prisma.QuizSetCreateNestedManyWithoutClassInput
|
||||
materialGroups?: Prisma.MaterialGroupCreateNestedManyWithoutClassInput
|
||||
}
|
||||
|
||||
export type ClassUncheckedCreateWithoutDecksInput = {
|
||||
|
|
@ -428,6 +450,7 @@ export type ClassUncheckedCreateWithoutDecksInput = {
|
|||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
quizSets?: Prisma.QuizSetUncheckedCreateNestedManyWithoutClassInput
|
||||
materialGroups?: Prisma.MaterialGroupUncheckedCreateNestedManyWithoutClassInput
|
||||
}
|
||||
|
||||
export type ClassCreateOrConnectWithoutDecksInput = {
|
||||
|
|
@ -453,6 +476,7 @@ export type ClassUpdateWithoutDecksInput = {
|
|||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
quizSets?: Prisma.QuizSetUpdateManyWithoutClassNestedInput
|
||||
materialGroups?: Prisma.MaterialGroupUpdateManyWithoutClassNestedInput
|
||||
}
|
||||
|
||||
export type ClassUncheckedUpdateWithoutDecksInput = {
|
||||
|
|
@ -462,6 +486,7 @@ export type ClassUncheckedUpdateWithoutDecksInput = {
|
|||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
quizSets?: Prisma.QuizSetUncheckedUpdateManyWithoutClassNestedInput
|
||||
materialGroups?: Prisma.MaterialGroupUncheckedUpdateManyWithoutClassNestedInput
|
||||
}
|
||||
|
||||
export type ClassCreateWithoutQuizSetsInput = {
|
||||
|
|
@ -471,6 +496,7 @@ export type ClassCreateWithoutQuizSetsInput = {
|
|||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
decks?: Prisma.DeckCreateNestedManyWithoutClassInput
|
||||
materialGroups?: Prisma.MaterialGroupCreateNestedManyWithoutClassInput
|
||||
}
|
||||
|
||||
export type ClassUncheckedCreateWithoutQuizSetsInput = {
|
||||
|
|
@ -480,6 +506,7 @@ export type ClassUncheckedCreateWithoutQuizSetsInput = {
|
|||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
decks?: Prisma.DeckUncheckedCreateNestedManyWithoutClassInput
|
||||
materialGroups?: Prisma.MaterialGroupUncheckedCreateNestedManyWithoutClassInput
|
||||
}
|
||||
|
||||
export type ClassCreateOrConnectWithoutQuizSetsInput = {
|
||||
|
|
@ -505,6 +532,7 @@ export type ClassUpdateWithoutQuizSetsInput = {
|
|||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
decks?: Prisma.DeckUpdateManyWithoutClassNestedInput
|
||||
materialGroups?: Prisma.MaterialGroupUpdateManyWithoutClassNestedInput
|
||||
}
|
||||
|
||||
export type ClassUncheckedUpdateWithoutQuizSetsInput = {
|
||||
|
|
@ -514,6 +542,63 @@ export type ClassUncheckedUpdateWithoutQuizSetsInput = {
|
|||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
decks?: Prisma.DeckUncheckedUpdateManyWithoutClassNestedInput
|
||||
materialGroups?: Prisma.MaterialGroupUncheckedUpdateManyWithoutClassNestedInput
|
||||
}
|
||||
|
||||
export type ClassCreateWithoutMaterialGroupsInput = {
|
||||
id?: string
|
||||
slug: string
|
||||
name: string
|
||||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
decks?: Prisma.DeckCreateNestedManyWithoutClassInput
|
||||
quizSets?: Prisma.QuizSetCreateNestedManyWithoutClassInput
|
||||
}
|
||||
|
||||
export type ClassUncheckedCreateWithoutMaterialGroupsInput = {
|
||||
id?: string
|
||||
slug: string
|
||||
name: string
|
||||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
decks?: Prisma.DeckUncheckedCreateNestedManyWithoutClassInput
|
||||
quizSets?: Prisma.QuizSetUncheckedCreateNestedManyWithoutClassInput
|
||||
}
|
||||
|
||||
export type ClassCreateOrConnectWithoutMaterialGroupsInput = {
|
||||
where: Prisma.ClassWhereUniqueInput
|
||||
create: Prisma.XOR<Prisma.ClassCreateWithoutMaterialGroupsInput, Prisma.ClassUncheckedCreateWithoutMaterialGroupsInput>
|
||||
}
|
||||
|
||||
export type ClassUpsertWithoutMaterialGroupsInput = {
|
||||
update: Prisma.XOR<Prisma.ClassUpdateWithoutMaterialGroupsInput, Prisma.ClassUncheckedUpdateWithoutMaterialGroupsInput>
|
||||
create: Prisma.XOR<Prisma.ClassCreateWithoutMaterialGroupsInput, Prisma.ClassUncheckedCreateWithoutMaterialGroupsInput>
|
||||
where?: Prisma.ClassWhereInput
|
||||
}
|
||||
|
||||
export type ClassUpdateToOneWithWhereWithoutMaterialGroupsInput = {
|
||||
where?: Prisma.ClassWhereInput
|
||||
data: Prisma.XOR<Prisma.ClassUpdateWithoutMaterialGroupsInput, Prisma.ClassUncheckedUpdateWithoutMaterialGroupsInput>
|
||||
}
|
||||
|
||||
export type ClassUpdateWithoutMaterialGroupsInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
slug?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
decks?: Prisma.DeckUpdateManyWithoutClassNestedInput
|
||||
quizSets?: Prisma.QuizSetUpdateManyWithoutClassNestedInput
|
||||
}
|
||||
|
||||
export type ClassUncheckedUpdateWithoutMaterialGroupsInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
slug?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
decks?: Prisma.DeckUncheckedUpdateManyWithoutClassNestedInput
|
||||
quizSets?: Prisma.QuizSetUncheckedUpdateManyWithoutClassNestedInput
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -524,11 +609,13 @@ export type ClassUncheckedUpdateWithoutQuizSetsInput = {
|
|||
export type ClassCountOutputType = {
|
||||
decks: number
|
||||
quizSets: number
|
||||
materialGroups: number
|
||||
}
|
||||
|
||||
export type ClassCountOutputTypeSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
decks?: boolean | ClassCountOutputTypeCountDecksArgs
|
||||
quizSets?: boolean | ClassCountOutputTypeCountQuizSetsArgs
|
||||
materialGroups?: boolean | ClassCountOutputTypeCountMaterialGroupsArgs
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -555,6 +642,13 @@ export type ClassCountOutputTypeCountQuizSetsArgs<ExtArgs extends runtime.Types.
|
|||
where?: Prisma.QuizSetWhereInput
|
||||
}
|
||||
|
||||
/**
|
||||
* ClassCountOutputType without action
|
||||
*/
|
||||
export type ClassCountOutputTypeCountMaterialGroupsArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
where?: Prisma.MaterialGroupWhereInput
|
||||
}
|
||||
|
||||
|
||||
export type ClassSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
||||
id?: boolean
|
||||
|
|
@ -564,6 +658,7 @@ export type ClassSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs =
|
|||
createdAt?: boolean
|
||||
decks?: boolean | Prisma.Class$decksArgs<ExtArgs>
|
||||
quizSets?: boolean | Prisma.Class$quizSetsArgs<ExtArgs>
|
||||
materialGroups?: boolean | Prisma.Class$materialGroupsArgs<ExtArgs>
|
||||
_count?: boolean | Prisma.ClassCountOutputTypeDefaultArgs<ExtArgs>
|
||||
}, ExtArgs["result"]["class"]>
|
||||
|
||||
|
|
@ -595,6 +690,7 @@ export type ClassOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = ru
|
|||
export type ClassInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
decks?: boolean | Prisma.Class$decksArgs<ExtArgs>
|
||||
quizSets?: boolean | Prisma.Class$quizSetsArgs<ExtArgs>
|
||||
materialGroups?: boolean | Prisma.Class$materialGroupsArgs<ExtArgs>
|
||||
_count?: boolean | Prisma.ClassCountOutputTypeDefaultArgs<ExtArgs>
|
||||
}
|
||||
export type ClassIncludeCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {}
|
||||
|
|
@ -605,6 +701,7 @@ export type $ClassPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs
|
|||
objects: {
|
||||
decks: Prisma.$DeckPayload<ExtArgs>[]
|
||||
quizSets: Prisma.$QuizSetPayload<ExtArgs>[]
|
||||
materialGroups: Prisma.$MaterialGroupPayload<ExtArgs>[]
|
||||
}
|
||||
scalars: runtime.Types.Extensions.GetPayloadResult<{
|
||||
id: string
|
||||
|
|
@ -1008,6 +1105,7 @@ export interface Prisma__ClassClient<T, Null = never, ExtArgs extends runtime.Ty
|
|||
readonly [Symbol.toStringTag]: "PrismaPromise"
|
||||
decks<T extends Prisma.Class$decksArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Class$decksArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$DeckPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||
quizSets<T extends Prisma.Class$quizSetsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Class$quizSetsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$QuizSetPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||
materialGroups<T extends Prisma.Class$materialGroupsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Class$materialGroupsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$MaterialGroupPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||
/**
|
||||
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
||||
* @param onfulfilled The callback to execute when the Promise is resolved.
|
||||
|
|
@ -1480,6 +1578,30 @@ export type Class$quizSetsArgs<ExtArgs extends runtime.Types.Extensions.Internal
|
|||
distinct?: Prisma.QuizSetScalarFieldEnum | Prisma.QuizSetScalarFieldEnum[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Class.materialGroups
|
||||
*/
|
||||
export type Class$materialGroupsArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
/**
|
||||
* Select specific fields to fetch from the MaterialGroup
|
||||
*/
|
||||
select?: Prisma.MaterialGroupSelect<ExtArgs> | null
|
||||
/**
|
||||
* Omit specific fields from the MaterialGroup
|
||||
*/
|
||||
omit?: Prisma.MaterialGroupOmit<ExtArgs> | null
|
||||
/**
|
||||
* Choose, which related nodes to fetch as well
|
||||
*/
|
||||
include?: Prisma.MaterialGroupInclude<ExtArgs> | null
|
||||
where?: Prisma.MaterialGroupWhereInput
|
||||
orderBy?: Prisma.MaterialGroupOrderByWithRelationInput | Prisma.MaterialGroupOrderByWithRelationInput[]
|
||||
cursor?: Prisma.MaterialGroupWhereUniqueInput
|
||||
take?: number
|
||||
skip?: number
|
||||
distinct?: Prisma.MaterialGroupScalarFieldEnum | Prisma.MaterialGroupScalarFieldEnum[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Class without action
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ export type DeckMinAggregateOutputType = {
|
|||
description: string | null
|
||||
sortOrder: number | null
|
||||
createdAt: Date | null
|
||||
groupId: string | null
|
||||
}
|
||||
|
||||
export type DeckMaxAggregateOutputType = {
|
||||
|
|
@ -50,6 +51,7 @@ export type DeckMaxAggregateOutputType = {
|
|||
description: string | null
|
||||
sortOrder: number | null
|
||||
createdAt: Date | null
|
||||
groupId: string | null
|
||||
}
|
||||
|
||||
export type DeckCountAggregateOutputType = {
|
||||
|
|
@ -59,6 +61,7 @@ export type DeckCountAggregateOutputType = {
|
|||
description: number
|
||||
sortOrder: number
|
||||
createdAt: number
|
||||
groupId: number
|
||||
_all: number
|
||||
}
|
||||
|
||||
|
|
@ -78,6 +81,7 @@ export type DeckMinAggregateInputType = {
|
|||
description?: true
|
||||
sortOrder?: true
|
||||
createdAt?: true
|
||||
groupId?: true
|
||||
}
|
||||
|
||||
export type DeckMaxAggregateInputType = {
|
||||
|
|
@ -87,6 +91,7 @@ export type DeckMaxAggregateInputType = {
|
|||
description?: true
|
||||
sortOrder?: true
|
||||
createdAt?: true
|
||||
groupId?: true
|
||||
}
|
||||
|
||||
export type DeckCountAggregateInputType = {
|
||||
|
|
@ -96,6 +101,7 @@ export type DeckCountAggregateInputType = {
|
|||
description?: true
|
||||
sortOrder?: true
|
||||
createdAt?: true
|
||||
groupId?: true
|
||||
_all?: true
|
||||
}
|
||||
|
||||
|
|
@ -192,6 +198,7 @@ export type DeckGroupByOutputType = {
|
|||
description: string | null
|
||||
sortOrder: number
|
||||
createdAt: Date
|
||||
groupId: string | null
|
||||
_count: DeckCountAggregateOutputType | null
|
||||
_avg: DeckAvgAggregateOutputType | null
|
||||
_sum: DeckSumAggregateOutputType | null
|
||||
|
|
@ -224,7 +231,9 @@ export type DeckWhereInput = {
|
|||
description?: Prisma.StringNullableFilter<"Deck"> | string | null
|
||||
sortOrder?: Prisma.IntFilter<"Deck"> | number
|
||||
createdAt?: Prisma.DateTimeFilter<"Deck"> | Date | string
|
||||
groupId?: Prisma.StringNullableFilter<"Deck"> | string | null
|
||||
class?: Prisma.XOR<Prisma.ClassScalarRelationFilter, Prisma.ClassWhereInput>
|
||||
group?: Prisma.XOR<Prisma.MaterialGroupNullableScalarRelationFilter, Prisma.MaterialGroupWhereInput> | null
|
||||
cards?: Prisma.FlashcardListRelationFilter
|
||||
shareLink?: Prisma.XOR<Prisma.ShareLinkNullableScalarRelationFilter, Prisma.ShareLinkWhereInput> | null
|
||||
progress?: Prisma.StudyProgressListRelationFilter
|
||||
|
|
@ -237,7 +246,9 @@ export type DeckOrderByWithRelationInput = {
|
|||
description?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
sortOrder?: Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
groupId?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
class?: Prisma.ClassOrderByWithRelationInput
|
||||
group?: Prisma.MaterialGroupOrderByWithRelationInput
|
||||
cards?: Prisma.FlashcardOrderByRelationAggregateInput
|
||||
shareLink?: Prisma.ShareLinkOrderByWithRelationInput
|
||||
progress?: Prisma.StudyProgressOrderByRelationAggregateInput
|
||||
|
|
@ -253,7 +264,9 @@ export type DeckWhereUniqueInput = Prisma.AtLeast<{
|
|||
description?: Prisma.StringNullableFilter<"Deck"> | string | null
|
||||
sortOrder?: Prisma.IntFilter<"Deck"> | number
|
||||
createdAt?: Prisma.DateTimeFilter<"Deck"> | Date | string
|
||||
groupId?: Prisma.StringNullableFilter<"Deck"> | string | null
|
||||
class?: Prisma.XOR<Prisma.ClassScalarRelationFilter, Prisma.ClassWhereInput>
|
||||
group?: Prisma.XOR<Prisma.MaterialGroupNullableScalarRelationFilter, Prisma.MaterialGroupWhereInput> | null
|
||||
cards?: Prisma.FlashcardListRelationFilter
|
||||
shareLink?: Prisma.XOR<Prisma.ShareLinkNullableScalarRelationFilter, Prisma.ShareLinkWhereInput> | null
|
||||
progress?: Prisma.StudyProgressListRelationFilter
|
||||
|
|
@ -266,6 +279,7 @@ export type DeckOrderByWithAggregationInput = {
|
|||
description?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
sortOrder?: Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
groupId?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
_count?: Prisma.DeckCountOrderByAggregateInput
|
||||
_avg?: Prisma.DeckAvgOrderByAggregateInput
|
||||
_max?: Prisma.DeckMaxOrderByAggregateInput
|
||||
|
|
@ -283,6 +297,7 @@ export type DeckScalarWhereWithAggregatesInput = {
|
|||
description?: Prisma.StringNullableWithAggregatesFilter<"Deck"> | string | null
|
||||
sortOrder?: Prisma.IntWithAggregatesFilter<"Deck"> | number
|
||||
createdAt?: Prisma.DateTimeWithAggregatesFilter<"Deck"> | Date | string
|
||||
groupId?: Prisma.StringNullableWithAggregatesFilter<"Deck"> | string | null
|
||||
}
|
||||
|
||||
export type DeckCreateInput = {
|
||||
|
|
@ -292,6 +307,7 @@ export type DeckCreateInput = {
|
|||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
class: Prisma.ClassCreateNestedOneWithoutDecksInput
|
||||
group?: Prisma.MaterialGroupCreateNestedOneWithoutDecksInput
|
||||
cards?: Prisma.FlashcardCreateNestedManyWithoutDeckInput
|
||||
shareLink?: Prisma.ShareLinkCreateNestedOneWithoutDeckInput
|
||||
progress?: Prisma.StudyProgressCreateNestedManyWithoutDeckInput
|
||||
|
|
@ -304,6 +320,7 @@ export type DeckUncheckedCreateInput = {
|
|||
description?: string | null
|
||||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
groupId?: string | null
|
||||
cards?: Prisma.FlashcardUncheckedCreateNestedManyWithoutDeckInput
|
||||
shareLink?: Prisma.ShareLinkUncheckedCreateNestedOneWithoutDeckInput
|
||||
progress?: Prisma.StudyProgressUncheckedCreateNestedManyWithoutDeckInput
|
||||
|
|
@ -316,6 +333,7 @@ export type DeckUpdateInput = {
|
|||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
class?: Prisma.ClassUpdateOneRequiredWithoutDecksNestedInput
|
||||
group?: Prisma.MaterialGroupUpdateOneWithoutDecksNestedInput
|
||||
cards?: Prisma.FlashcardUpdateManyWithoutDeckNestedInput
|
||||
shareLink?: Prisma.ShareLinkUpdateOneWithoutDeckNestedInput
|
||||
progress?: Prisma.StudyProgressUpdateManyWithoutDeckNestedInput
|
||||
|
|
@ -328,6 +346,7 @@ export type DeckUncheckedUpdateInput = {
|
|||
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
groupId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
cards?: Prisma.FlashcardUncheckedUpdateManyWithoutDeckNestedInput
|
||||
shareLink?: Prisma.ShareLinkUncheckedUpdateOneWithoutDeckNestedInput
|
||||
progress?: Prisma.StudyProgressUncheckedUpdateManyWithoutDeckNestedInput
|
||||
|
|
@ -340,6 +359,7 @@ export type DeckCreateManyInput = {
|
|||
description?: string | null
|
||||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
groupId?: string | null
|
||||
}
|
||||
|
||||
export type DeckUpdateManyMutationInput = {
|
||||
|
|
@ -357,6 +377,7 @@ export type DeckUncheckedUpdateManyInput = {
|
|||
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
groupId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
}
|
||||
|
||||
export type DeckListRelationFilter = {
|
||||
|
|
@ -376,6 +397,7 @@ export type DeckCountOrderByAggregateInput = {
|
|||
description?: Prisma.SortOrder
|
||||
sortOrder?: Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
groupId?: Prisma.SortOrder
|
||||
}
|
||||
|
||||
export type DeckAvgOrderByAggregateInput = {
|
||||
|
|
@ -389,6 +411,7 @@ export type DeckMaxOrderByAggregateInput = {
|
|||
description?: Prisma.SortOrder
|
||||
sortOrder?: Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
groupId?: Prisma.SortOrder
|
||||
}
|
||||
|
||||
export type DeckMinOrderByAggregateInput = {
|
||||
|
|
@ -398,6 +421,7 @@ export type DeckMinOrderByAggregateInput = {
|
|||
description?: Prisma.SortOrder
|
||||
sortOrder?: Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
groupId?: Prisma.SortOrder
|
||||
}
|
||||
|
||||
export type DeckSumOrderByAggregateInput = {
|
||||
|
|
@ -506,12 +530,55 @@ export type DeckUpdateOneWithoutShareLinkNestedInput = {
|
|||
update?: Prisma.XOR<Prisma.XOR<Prisma.DeckUpdateToOneWithWhereWithoutShareLinkInput, Prisma.DeckUpdateWithoutShareLinkInput>, Prisma.DeckUncheckedUpdateWithoutShareLinkInput>
|
||||
}
|
||||
|
||||
export type DeckCreateNestedManyWithoutGroupInput = {
|
||||
create?: Prisma.XOR<Prisma.DeckCreateWithoutGroupInput, Prisma.DeckUncheckedCreateWithoutGroupInput> | Prisma.DeckCreateWithoutGroupInput[] | Prisma.DeckUncheckedCreateWithoutGroupInput[]
|
||||
connectOrCreate?: Prisma.DeckCreateOrConnectWithoutGroupInput | Prisma.DeckCreateOrConnectWithoutGroupInput[]
|
||||
createMany?: Prisma.DeckCreateManyGroupInputEnvelope
|
||||
connect?: Prisma.DeckWhereUniqueInput | Prisma.DeckWhereUniqueInput[]
|
||||
}
|
||||
|
||||
export type DeckUncheckedCreateNestedManyWithoutGroupInput = {
|
||||
create?: Prisma.XOR<Prisma.DeckCreateWithoutGroupInput, Prisma.DeckUncheckedCreateWithoutGroupInput> | Prisma.DeckCreateWithoutGroupInput[] | Prisma.DeckUncheckedCreateWithoutGroupInput[]
|
||||
connectOrCreate?: Prisma.DeckCreateOrConnectWithoutGroupInput | Prisma.DeckCreateOrConnectWithoutGroupInput[]
|
||||
createMany?: Prisma.DeckCreateManyGroupInputEnvelope
|
||||
connect?: Prisma.DeckWhereUniqueInput | Prisma.DeckWhereUniqueInput[]
|
||||
}
|
||||
|
||||
export type DeckUpdateManyWithoutGroupNestedInput = {
|
||||
create?: Prisma.XOR<Prisma.DeckCreateWithoutGroupInput, Prisma.DeckUncheckedCreateWithoutGroupInput> | Prisma.DeckCreateWithoutGroupInput[] | Prisma.DeckUncheckedCreateWithoutGroupInput[]
|
||||
connectOrCreate?: Prisma.DeckCreateOrConnectWithoutGroupInput | Prisma.DeckCreateOrConnectWithoutGroupInput[]
|
||||
upsert?: Prisma.DeckUpsertWithWhereUniqueWithoutGroupInput | Prisma.DeckUpsertWithWhereUniqueWithoutGroupInput[]
|
||||
createMany?: Prisma.DeckCreateManyGroupInputEnvelope
|
||||
set?: Prisma.DeckWhereUniqueInput | Prisma.DeckWhereUniqueInput[]
|
||||
disconnect?: Prisma.DeckWhereUniqueInput | Prisma.DeckWhereUniqueInput[]
|
||||
delete?: Prisma.DeckWhereUniqueInput | Prisma.DeckWhereUniqueInput[]
|
||||
connect?: Prisma.DeckWhereUniqueInput | Prisma.DeckWhereUniqueInput[]
|
||||
update?: Prisma.DeckUpdateWithWhereUniqueWithoutGroupInput | Prisma.DeckUpdateWithWhereUniqueWithoutGroupInput[]
|
||||
updateMany?: Prisma.DeckUpdateManyWithWhereWithoutGroupInput | Prisma.DeckUpdateManyWithWhereWithoutGroupInput[]
|
||||
deleteMany?: Prisma.DeckScalarWhereInput | Prisma.DeckScalarWhereInput[]
|
||||
}
|
||||
|
||||
export type DeckUncheckedUpdateManyWithoutGroupNestedInput = {
|
||||
create?: Prisma.XOR<Prisma.DeckCreateWithoutGroupInput, Prisma.DeckUncheckedCreateWithoutGroupInput> | Prisma.DeckCreateWithoutGroupInput[] | Prisma.DeckUncheckedCreateWithoutGroupInput[]
|
||||
connectOrCreate?: Prisma.DeckCreateOrConnectWithoutGroupInput | Prisma.DeckCreateOrConnectWithoutGroupInput[]
|
||||
upsert?: Prisma.DeckUpsertWithWhereUniqueWithoutGroupInput | Prisma.DeckUpsertWithWhereUniqueWithoutGroupInput[]
|
||||
createMany?: Prisma.DeckCreateManyGroupInputEnvelope
|
||||
set?: Prisma.DeckWhereUniqueInput | Prisma.DeckWhereUniqueInput[]
|
||||
disconnect?: Prisma.DeckWhereUniqueInput | Prisma.DeckWhereUniqueInput[]
|
||||
delete?: Prisma.DeckWhereUniqueInput | Prisma.DeckWhereUniqueInput[]
|
||||
connect?: Prisma.DeckWhereUniqueInput | Prisma.DeckWhereUniqueInput[]
|
||||
update?: Prisma.DeckUpdateWithWhereUniqueWithoutGroupInput | Prisma.DeckUpdateWithWhereUniqueWithoutGroupInput[]
|
||||
updateMany?: Prisma.DeckUpdateManyWithWhereWithoutGroupInput | Prisma.DeckUpdateManyWithWhereWithoutGroupInput[]
|
||||
deleteMany?: Prisma.DeckScalarWhereInput | Prisma.DeckScalarWhereInput[]
|
||||
}
|
||||
|
||||
export type DeckCreateWithoutClassInput = {
|
||||
id?: string
|
||||
name: string
|
||||
description?: string | null
|
||||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
group?: Prisma.MaterialGroupCreateNestedOneWithoutDecksInput
|
||||
cards?: Prisma.FlashcardCreateNestedManyWithoutDeckInput
|
||||
shareLink?: Prisma.ShareLinkCreateNestedOneWithoutDeckInput
|
||||
progress?: Prisma.StudyProgressCreateNestedManyWithoutDeckInput
|
||||
|
|
@ -523,6 +590,7 @@ export type DeckUncheckedCreateWithoutClassInput = {
|
|||
description?: string | null
|
||||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
groupId?: string | null
|
||||
cards?: Prisma.FlashcardUncheckedCreateNestedManyWithoutDeckInput
|
||||
shareLink?: Prisma.ShareLinkUncheckedCreateNestedOneWithoutDeckInput
|
||||
progress?: Prisma.StudyProgressUncheckedCreateNestedManyWithoutDeckInput
|
||||
|
|
@ -563,6 +631,7 @@ export type DeckScalarWhereInput = {
|
|||
description?: Prisma.StringNullableFilter<"Deck"> | string | null
|
||||
sortOrder?: Prisma.IntFilter<"Deck"> | number
|
||||
createdAt?: Prisma.DateTimeFilter<"Deck"> | Date | string
|
||||
groupId?: Prisma.StringNullableFilter<"Deck"> | string | null
|
||||
}
|
||||
|
||||
export type DeckCreateWithoutCardsInput = {
|
||||
|
|
@ -572,6 +641,7 @@ export type DeckCreateWithoutCardsInput = {
|
|||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
class: Prisma.ClassCreateNestedOneWithoutDecksInput
|
||||
group?: Prisma.MaterialGroupCreateNestedOneWithoutDecksInput
|
||||
shareLink?: Prisma.ShareLinkCreateNestedOneWithoutDeckInput
|
||||
progress?: Prisma.StudyProgressCreateNestedManyWithoutDeckInput
|
||||
}
|
||||
|
|
@ -583,6 +653,7 @@ export type DeckUncheckedCreateWithoutCardsInput = {
|
|||
description?: string | null
|
||||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
groupId?: string | null
|
||||
shareLink?: Prisma.ShareLinkUncheckedCreateNestedOneWithoutDeckInput
|
||||
progress?: Prisma.StudyProgressUncheckedCreateNestedManyWithoutDeckInput
|
||||
}
|
||||
|
|
@ -610,6 +681,7 @@ export type DeckUpdateWithoutCardsInput = {
|
|||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
class?: Prisma.ClassUpdateOneRequiredWithoutDecksNestedInput
|
||||
group?: Prisma.MaterialGroupUpdateOneWithoutDecksNestedInput
|
||||
shareLink?: Prisma.ShareLinkUpdateOneWithoutDeckNestedInput
|
||||
progress?: Prisma.StudyProgressUpdateManyWithoutDeckNestedInput
|
||||
}
|
||||
|
|
@ -621,6 +693,7 @@ export type DeckUncheckedUpdateWithoutCardsInput = {
|
|||
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
groupId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
shareLink?: Prisma.ShareLinkUncheckedUpdateOneWithoutDeckNestedInput
|
||||
progress?: Prisma.StudyProgressUncheckedUpdateManyWithoutDeckNestedInput
|
||||
}
|
||||
|
|
@ -632,6 +705,7 @@ export type DeckCreateWithoutProgressInput = {
|
|||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
class: Prisma.ClassCreateNestedOneWithoutDecksInput
|
||||
group?: Prisma.MaterialGroupCreateNestedOneWithoutDecksInput
|
||||
cards?: Prisma.FlashcardCreateNestedManyWithoutDeckInput
|
||||
shareLink?: Prisma.ShareLinkCreateNestedOneWithoutDeckInput
|
||||
}
|
||||
|
|
@ -643,6 +717,7 @@ export type DeckUncheckedCreateWithoutProgressInput = {
|
|||
description?: string | null
|
||||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
groupId?: string | null
|
||||
cards?: Prisma.FlashcardUncheckedCreateNestedManyWithoutDeckInput
|
||||
shareLink?: Prisma.ShareLinkUncheckedCreateNestedOneWithoutDeckInput
|
||||
}
|
||||
|
|
@ -670,6 +745,7 @@ export type DeckUpdateWithoutProgressInput = {
|
|||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
class?: Prisma.ClassUpdateOneRequiredWithoutDecksNestedInput
|
||||
group?: Prisma.MaterialGroupUpdateOneWithoutDecksNestedInput
|
||||
cards?: Prisma.FlashcardUpdateManyWithoutDeckNestedInput
|
||||
shareLink?: Prisma.ShareLinkUpdateOneWithoutDeckNestedInput
|
||||
}
|
||||
|
|
@ -681,6 +757,7 @@ export type DeckUncheckedUpdateWithoutProgressInput = {
|
|||
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
groupId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
cards?: Prisma.FlashcardUncheckedUpdateManyWithoutDeckNestedInput
|
||||
shareLink?: Prisma.ShareLinkUncheckedUpdateOneWithoutDeckNestedInput
|
||||
}
|
||||
|
|
@ -692,6 +769,7 @@ export type DeckCreateWithoutShareLinkInput = {
|
|||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
class: Prisma.ClassCreateNestedOneWithoutDecksInput
|
||||
group?: Prisma.MaterialGroupCreateNestedOneWithoutDecksInput
|
||||
cards?: Prisma.FlashcardCreateNestedManyWithoutDeckInput
|
||||
progress?: Prisma.StudyProgressCreateNestedManyWithoutDeckInput
|
||||
}
|
||||
|
|
@ -703,6 +781,7 @@ export type DeckUncheckedCreateWithoutShareLinkInput = {
|
|||
description?: string | null
|
||||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
groupId?: string | null
|
||||
cards?: Prisma.FlashcardUncheckedCreateNestedManyWithoutDeckInput
|
||||
progress?: Prisma.StudyProgressUncheckedCreateNestedManyWithoutDeckInput
|
||||
}
|
||||
|
|
@ -730,6 +809,7 @@ export type DeckUpdateWithoutShareLinkInput = {
|
|||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
class?: Prisma.ClassUpdateOneRequiredWithoutDecksNestedInput
|
||||
group?: Prisma.MaterialGroupUpdateOneWithoutDecksNestedInput
|
||||
cards?: Prisma.FlashcardUpdateManyWithoutDeckNestedInput
|
||||
progress?: Prisma.StudyProgressUpdateManyWithoutDeckNestedInput
|
||||
}
|
||||
|
|
@ -741,16 +821,67 @@ export type DeckUncheckedUpdateWithoutShareLinkInput = {
|
|||
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
groupId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
cards?: Prisma.FlashcardUncheckedUpdateManyWithoutDeckNestedInput
|
||||
progress?: Prisma.StudyProgressUncheckedUpdateManyWithoutDeckNestedInput
|
||||
}
|
||||
|
||||
export type DeckCreateWithoutGroupInput = {
|
||||
id?: string
|
||||
name: string
|
||||
description?: string | null
|
||||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
class: Prisma.ClassCreateNestedOneWithoutDecksInput
|
||||
cards?: Prisma.FlashcardCreateNestedManyWithoutDeckInput
|
||||
shareLink?: Prisma.ShareLinkCreateNestedOneWithoutDeckInput
|
||||
progress?: Prisma.StudyProgressCreateNestedManyWithoutDeckInput
|
||||
}
|
||||
|
||||
export type DeckUncheckedCreateWithoutGroupInput = {
|
||||
id?: string
|
||||
classId: string
|
||||
name: string
|
||||
description?: string | null
|
||||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
cards?: Prisma.FlashcardUncheckedCreateNestedManyWithoutDeckInput
|
||||
shareLink?: Prisma.ShareLinkUncheckedCreateNestedOneWithoutDeckInput
|
||||
progress?: Prisma.StudyProgressUncheckedCreateNestedManyWithoutDeckInput
|
||||
}
|
||||
|
||||
export type DeckCreateOrConnectWithoutGroupInput = {
|
||||
where: Prisma.DeckWhereUniqueInput
|
||||
create: Prisma.XOR<Prisma.DeckCreateWithoutGroupInput, Prisma.DeckUncheckedCreateWithoutGroupInput>
|
||||
}
|
||||
|
||||
export type DeckCreateManyGroupInputEnvelope = {
|
||||
data: Prisma.DeckCreateManyGroupInput | Prisma.DeckCreateManyGroupInput[]
|
||||
}
|
||||
|
||||
export type DeckUpsertWithWhereUniqueWithoutGroupInput = {
|
||||
where: Prisma.DeckWhereUniqueInput
|
||||
update: Prisma.XOR<Prisma.DeckUpdateWithoutGroupInput, Prisma.DeckUncheckedUpdateWithoutGroupInput>
|
||||
create: Prisma.XOR<Prisma.DeckCreateWithoutGroupInput, Prisma.DeckUncheckedCreateWithoutGroupInput>
|
||||
}
|
||||
|
||||
export type DeckUpdateWithWhereUniqueWithoutGroupInput = {
|
||||
where: Prisma.DeckWhereUniqueInput
|
||||
data: Prisma.XOR<Prisma.DeckUpdateWithoutGroupInput, Prisma.DeckUncheckedUpdateWithoutGroupInput>
|
||||
}
|
||||
|
||||
export type DeckUpdateManyWithWhereWithoutGroupInput = {
|
||||
where: Prisma.DeckScalarWhereInput
|
||||
data: Prisma.XOR<Prisma.DeckUpdateManyMutationInput, Prisma.DeckUncheckedUpdateManyWithoutGroupInput>
|
||||
}
|
||||
|
||||
export type DeckCreateManyClassInput = {
|
||||
id?: string
|
||||
name: string
|
||||
description?: string | null
|
||||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
groupId?: string | null
|
||||
}
|
||||
|
||||
export type DeckUpdateWithoutClassInput = {
|
||||
|
|
@ -759,6 +890,7 @@ export type DeckUpdateWithoutClassInput = {
|
|||
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
group?: Prisma.MaterialGroupUpdateOneWithoutDecksNestedInput
|
||||
cards?: Prisma.FlashcardUpdateManyWithoutDeckNestedInput
|
||||
shareLink?: Prisma.ShareLinkUpdateOneWithoutDeckNestedInput
|
||||
progress?: Prisma.StudyProgressUpdateManyWithoutDeckNestedInput
|
||||
|
|
@ -770,6 +902,7 @@ export type DeckUncheckedUpdateWithoutClassInput = {
|
|||
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
groupId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
cards?: Prisma.FlashcardUncheckedUpdateManyWithoutDeckNestedInput
|
||||
shareLink?: Prisma.ShareLinkUncheckedUpdateOneWithoutDeckNestedInput
|
||||
progress?: Prisma.StudyProgressUncheckedUpdateManyWithoutDeckNestedInput
|
||||
|
|
@ -781,6 +914,49 @@ export type DeckUncheckedUpdateManyWithoutClassInput = {
|
|||
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
groupId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
}
|
||||
|
||||
export type DeckCreateManyGroupInput = {
|
||||
id?: string
|
||||
classId: string
|
||||
name: string
|
||||
description?: string | null
|
||||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
}
|
||||
|
||||
export type DeckUpdateWithoutGroupInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
class?: Prisma.ClassUpdateOneRequiredWithoutDecksNestedInput
|
||||
cards?: Prisma.FlashcardUpdateManyWithoutDeckNestedInput
|
||||
shareLink?: Prisma.ShareLinkUpdateOneWithoutDeckNestedInput
|
||||
progress?: Prisma.StudyProgressUpdateManyWithoutDeckNestedInput
|
||||
}
|
||||
|
||||
export type DeckUncheckedUpdateWithoutGroupInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
classId?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
cards?: Prisma.FlashcardUncheckedUpdateManyWithoutDeckNestedInput
|
||||
shareLink?: Prisma.ShareLinkUncheckedUpdateOneWithoutDeckNestedInput
|
||||
progress?: Prisma.StudyProgressUncheckedUpdateManyWithoutDeckNestedInput
|
||||
}
|
||||
|
||||
export type DeckUncheckedUpdateManyWithoutGroupInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
classId?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -830,7 +1006,9 @@ export type DeckSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = r
|
|||
description?: boolean
|
||||
sortOrder?: boolean
|
||||
createdAt?: boolean
|
||||
groupId?: boolean
|
||||
class?: boolean | Prisma.ClassDefaultArgs<ExtArgs>
|
||||
group?: boolean | Prisma.Deck$groupArgs<ExtArgs>
|
||||
cards?: boolean | Prisma.Deck$cardsArgs<ExtArgs>
|
||||
shareLink?: boolean | Prisma.Deck$shareLinkArgs<ExtArgs>
|
||||
progress?: boolean | Prisma.Deck$progressArgs<ExtArgs>
|
||||
|
|
@ -844,7 +1022,9 @@ export type DeckSelectCreateManyAndReturn<ExtArgs extends runtime.Types.Extensio
|
|||
description?: boolean
|
||||
sortOrder?: boolean
|
||||
createdAt?: boolean
|
||||
groupId?: boolean
|
||||
class?: boolean | Prisma.ClassDefaultArgs<ExtArgs>
|
||||
group?: boolean | Prisma.Deck$groupArgs<ExtArgs>
|
||||
}, ExtArgs["result"]["deck"]>
|
||||
|
||||
export type DeckSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
||||
|
|
@ -854,7 +1034,9 @@ export type DeckSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensio
|
|||
description?: boolean
|
||||
sortOrder?: boolean
|
||||
createdAt?: boolean
|
||||
groupId?: boolean
|
||||
class?: boolean | Prisma.ClassDefaultArgs<ExtArgs>
|
||||
group?: boolean | Prisma.Deck$groupArgs<ExtArgs>
|
||||
}, ExtArgs["result"]["deck"]>
|
||||
|
||||
export type DeckSelectScalar = {
|
||||
|
|
@ -864,11 +1046,13 @@ export type DeckSelectScalar = {
|
|||
description?: boolean
|
||||
sortOrder?: boolean
|
||||
createdAt?: boolean
|
||||
groupId?: boolean
|
||||
}
|
||||
|
||||
export type DeckOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "classId" | "name" | "description" | "sortOrder" | "createdAt", ExtArgs["result"]["deck"]>
|
||||
export type DeckOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "classId" | "name" | "description" | "sortOrder" | "createdAt" | "groupId", ExtArgs["result"]["deck"]>
|
||||
export type DeckInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
class?: boolean | Prisma.ClassDefaultArgs<ExtArgs>
|
||||
group?: boolean | Prisma.Deck$groupArgs<ExtArgs>
|
||||
cards?: boolean | Prisma.Deck$cardsArgs<ExtArgs>
|
||||
shareLink?: boolean | Prisma.Deck$shareLinkArgs<ExtArgs>
|
||||
progress?: boolean | Prisma.Deck$progressArgs<ExtArgs>
|
||||
|
|
@ -876,15 +1060,18 @@ export type DeckInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs =
|
|||
}
|
||||
export type DeckIncludeCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
class?: boolean | Prisma.ClassDefaultArgs<ExtArgs>
|
||||
group?: boolean | Prisma.Deck$groupArgs<ExtArgs>
|
||||
}
|
||||
export type DeckIncludeUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
class?: boolean | Prisma.ClassDefaultArgs<ExtArgs>
|
||||
group?: boolean | Prisma.Deck$groupArgs<ExtArgs>
|
||||
}
|
||||
|
||||
export type $DeckPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
name: "Deck"
|
||||
objects: {
|
||||
class: Prisma.$ClassPayload<ExtArgs>
|
||||
group: Prisma.$MaterialGroupPayload<ExtArgs> | null
|
||||
cards: Prisma.$FlashcardPayload<ExtArgs>[]
|
||||
shareLink: Prisma.$ShareLinkPayload<ExtArgs> | null
|
||||
progress: Prisma.$StudyProgressPayload<ExtArgs>[]
|
||||
|
|
@ -896,6 +1083,7 @@ export type $DeckPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs =
|
|||
description: string | null
|
||||
sortOrder: number
|
||||
createdAt: Date
|
||||
groupId: string | null
|
||||
}, ExtArgs["result"]["deck"]>
|
||||
composites: {}
|
||||
}
|
||||
|
|
@ -1291,6 +1479,7 @@ readonly fields: DeckFieldRefs;
|
|||
export interface Prisma__DeckClient<T, Null = never, ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs, GlobalOmitOptions = {}> extends Prisma.PrismaPromise<T> {
|
||||
readonly [Symbol.toStringTag]: "PrismaPromise"
|
||||
class<T extends Prisma.ClassDefaultArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.ClassDefaultArgs<ExtArgs>>): Prisma.Prisma__ClassClient<runtime.Types.Result.GetResult<Prisma.$ClassPayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions>
|
||||
group<T extends Prisma.Deck$groupArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Deck$groupArgs<ExtArgs>>): Prisma.Prisma__MaterialGroupClient<runtime.Types.Result.GetResult<Prisma.$MaterialGroupPayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions>
|
||||
cards<T extends Prisma.Deck$cardsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Deck$cardsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$FlashcardPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||
shareLink<T extends Prisma.Deck$shareLinkArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Deck$shareLinkArgs<ExtArgs>>): Prisma.Prisma__ShareLinkClient<runtime.Types.Result.GetResult<Prisma.$ShareLinkPayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions>
|
||||
progress<T extends Prisma.Deck$progressArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Deck$progressArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$StudyProgressPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||
|
|
@ -1329,6 +1518,7 @@ export interface DeckFieldRefs {
|
|||
readonly description: Prisma.FieldRef<"Deck", 'String'>
|
||||
readonly sortOrder: Prisma.FieldRef<"Deck", 'Int'>
|
||||
readonly createdAt: Prisma.FieldRef<"Deck", 'DateTime'>
|
||||
readonly groupId: Prisma.FieldRef<"Deck", 'String'>
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1727,6 +1917,25 @@ export type DeckDeleteManyArgs<ExtArgs extends runtime.Types.Extensions.Internal
|
|||
limit?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Deck.group
|
||||
*/
|
||||
export type Deck$groupArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
/**
|
||||
* Select specific fields to fetch from the MaterialGroup
|
||||
*/
|
||||
select?: Prisma.MaterialGroupSelect<ExtArgs> | null
|
||||
/**
|
||||
* Omit specific fields from the MaterialGroup
|
||||
*/
|
||||
omit?: Prisma.MaterialGroupOmit<ExtArgs> | null
|
||||
/**
|
||||
* Choose, which related nodes to fetch as well
|
||||
*/
|
||||
include?: Prisma.MaterialGroupInclude<ExtArgs> | null
|
||||
where?: Prisma.MaterialGroupWhereInput
|
||||
}
|
||||
|
||||
/**
|
||||
* Deck.cards
|
||||
*/
|
||||
|
|
|
|||
1806
src/generated/prisma/models/MaterialGroup.ts
Normal file
1806
src/generated/prisma/models/MaterialGroup.ts
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -41,6 +41,7 @@ export type QuizSetMinAggregateOutputType = {
|
|||
description: string | null
|
||||
sortOrder: number | null
|
||||
createdAt: Date | null
|
||||
groupId: string | null
|
||||
}
|
||||
|
||||
export type QuizSetMaxAggregateOutputType = {
|
||||
|
|
@ -50,6 +51,7 @@ export type QuizSetMaxAggregateOutputType = {
|
|||
description: string | null
|
||||
sortOrder: number | null
|
||||
createdAt: Date | null
|
||||
groupId: string | null
|
||||
}
|
||||
|
||||
export type QuizSetCountAggregateOutputType = {
|
||||
|
|
@ -59,6 +61,7 @@ export type QuizSetCountAggregateOutputType = {
|
|||
description: number
|
||||
sortOrder: number
|
||||
createdAt: number
|
||||
groupId: number
|
||||
_all: number
|
||||
}
|
||||
|
||||
|
|
@ -78,6 +81,7 @@ export type QuizSetMinAggregateInputType = {
|
|||
description?: true
|
||||
sortOrder?: true
|
||||
createdAt?: true
|
||||
groupId?: true
|
||||
}
|
||||
|
||||
export type QuizSetMaxAggregateInputType = {
|
||||
|
|
@ -87,6 +91,7 @@ export type QuizSetMaxAggregateInputType = {
|
|||
description?: true
|
||||
sortOrder?: true
|
||||
createdAt?: true
|
||||
groupId?: true
|
||||
}
|
||||
|
||||
export type QuizSetCountAggregateInputType = {
|
||||
|
|
@ -96,6 +101,7 @@ export type QuizSetCountAggregateInputType = {
|
|||
description?: true
|
||||
sortOrder?: true
|
||||
createdAt?: true
|
||||
groupId?: true
|
||||
_all?: true
|
||||
}
|
||||
|
||||
|
|
@ -192,6 +198,7 @@ export type QuizSetGroupByOutputType = {
|
|||
description: string | null
|
||||
sortOrder: number
|
||||
createdAt: Date
|
||||
groupId: string | null
|
||||
_count: QuizSetCountAggregateOutputType | null
|
||||
_avg: QuizSetAvgAggregateOutputType | null
|
||||
_sum: QuizSetSumAggregateOutputType | null
|
||||
|
|
@ -224,7 +231,9 @@ export type QuizSetWhereInput = {
|
|||
description?: Prisma.StringNullableFilter<"QuizSet"> | string | null
|
||||
sortOrder?: Prisma.IntFilter<"QuizSet"> | number
|
||||
createdAt?: Prisma.DateTimeFilter<"QuizSet"> | Date | string
|
||||
groupId?: Prisma.StringNullableFilter<"QuizSet"> | string | null
|
||||
class?: Prisma.XOR<Prisma.ClassScalarRelationFilter, Prisma.ClassWhereInput>
|
||||
group?: Prisma.XOR<Prisma.MaterialGroupNullableScalarRelationFilter, Prisma.MaterialGroupWhereInput> | null
|
||||
questions?: Prisma.QuestionListRelationFilter
|
||||
attempts?: Prisma.QuizAttemptListRelationFilter
|
||||
shareLink?: Prisma.XOR<Prisma.ShareLinkNullableScalarRelationFilter, Prisma.ShareLinkWhereInput> | null
|
||||
|
|
@ -238,7 +247,9 @@ export type QuizSetOrderByWithRelationInput = {
|
|||
description?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
sortOrder?: Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
groupId?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
class?: Prisma.ClassOrderByWithRelationInput
|
||||
group?: Prisma.MaterialGroupOrderByWithRelationInput
|
||||
questions?: Prisma.QuestionOrderByRelationAggregateInput
|
||||
attempts?: Prisma.QuizAttemptOrderByRelationAggregateInput
|
||||
shareLink?: Prisma.ShareLinkOrderByWithRelationInput
|
||||
|
|
@ -255,7 +266,9 @@ export type QuizSetWhereUniqueInput = Prisma.AtLeast<{
|
|||
description?: Prisma.StringNullableFilter<"QuizSet"> | string | null
|
||||
sortOrder?: Prisma.IntFilter<"QuizSet"> | number
|
||||
createdAt?: Prisma.DateTimeFilter<"QuizSet"> | Date | string
|
||||
groupId?: Prisma.StringNullableFilter<"QuizSet"> | string | null
|
||||
class?: Prisma.XOR<Prisma.ClassScalarRelationFilter, Prisma.ClassWhereInput>
|
||||
group?: Prisma.XOR<Prisma.MaterialGroupNullableScalarRelationFilter, Prisma.MaterialGroupWhereInput> | null
|
||||
questions?: Prisma.QuestionListRelationFilter
|
||||
attempts?: Prisma.QuizAttemptListRelationFilter
|
||||
shareLink?: Prisma.XOR<Prisma.ShareLinkNullableScalarRelationFilter, Prisma.ShareLinkWhereInput> | null
|
||||
|
|
@ -269,6 +282,7 @@ export type QuizSetOrderByWithAggregationInput = {
|
|||
description?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
sortOrder?: Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
groupId?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
_count?: Prisma.QuizSetCountOrderByAggregateInput
|
||||
_avg?: Prisma.QuizSetAvgOrderByAggregateInput
|
||||
_max?: Prisma.QuizSetMaxOrderByAggregateInput
|
||||
|
|
@ -286,6 +300,7 @@ export type QuizSetScalarWhereWithAggregatesInput = {
|
|||
description?: Prisma.StringNullableWithAggregatesFilter<"QuizSet"> | string | null
|
||||
sortOrder?: Prisma.IntWithAggregatesFilter<"QuizSet"> | number
|
||||
createdAt?: Prisma.DateTimeWithAggregatesFilter<"QuizSet"> | Date | string
|
||||
groupId?: Prisma.StringNullableWithAggregatesFilter<"QuizSet"> | string | null
|
||||
}
|
||||
|
||||
export type QuizSetCreateInput = {
|
||||
|
|
@ -295,6 +310,7 @@ export type QuizSetCreateInput = {
|
|||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
class: Prisma.ClassCreateNestedOneWithoutQuizSetsInput
|
||||
group?: Prisma.MaterialGroupCreateNestedOneWithoutQuizSetsInput
|
||||
questions?: Prisma.QuestionCreateNestedManyWithoutQuizSetInput
|
||||
attempts?: Prisma.QuizAttemptCreateNestedManyWithoutQuizSetInput
|
||||
shareLink?: Prisma.ShareLinkCreateNestedOneWithoutQuizSetInput
|
||||
|
|
@ -308,6 +324,7 @@ export type QuizSetUncheckedCreateInput = {
|
|||
description?: string | null
|
||||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
groupId?: string | null
|
||||
questions?: Prisma.QuestionUncheckedCreateNestedManyWithoutQuizSetInput
|
||||
attempts?: Prisma.QuizAttemptUncheckedCreateNestedManyWithoutQuizSetInput
|
||||
shareLink?: Prisma.ShareLinkUncheckedCreateNestedOneWithoutQuizSetInput
|
||||
|
|
@ -321,6 +338,7 @@ export type QuizSetUpdateInput = {
|
|||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
class?: Prisma.ClassUpdateOneRequiredWithoutQuizSetsNestedInput
|
||||
group?: Prisma.MaterialGroupUpdateOneWithoutQuizSetsNestedInput
|
||||
questions?: Prisma.QuestionUpdateManyWithoutQuizSetNestedInput
|
||||
attempts?: Prisma.QuizAttemptUpdateManyWithoutQuizSetNestedInput
|
||||
shareLink?: Prisma.ShareLinkUpdateOneWithoutQuizSetNestedInput
|
||||
|
|
@ -334,6 +352,7 @@ export type QuizSetUncheckedUpdateInput = {
|
|||
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
groupId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
questions?: Prisma.QuestionUncheckedUpdateManyWithoutQuizSetNestedInput
|
||||
attempts?: Prisma.QuizAttemptUncheckedUpdateManyWithoutQuizSetNestedInput
|
||||
shareLink?: Prisma.ShareLinkUncheckedUpdateOneWithoutQuizSetNestedInput
|
||||
|
|
@ -347,6 +366,7 @@ export type QuizSetCreateManyInput = {
|
|||
description?: string | null
|
||||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
groupId?: string | null
|
||||
}
|
||||
|
||||
export type QuizSetUpdateManyMutationInput = {
|
||||
|
|
@ -364,6 +384,7 @@ export type QuizSetUncheckedUpdateManyInput = {
|
|||
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
groupId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
}
|
||||
|
||||
export type QuizSetListRelationFilter = {
|
||||
|
|
@ -383,6 +404,7 @@ export type QuizSetCountOrderByAggregateInput = {
|
|||
description?: Prisma.SortOrder
|
||||
sortOrder?: Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
groupId?: Prisma.SortOrder
|
||||
}
|
||||
|
||||
export type QuizSetAvgOrderByAggregateInput = {
|
||||
|
|
@ -396,6 +418,7 @@ export type QuizSetMaxOrderByAggregateInput = {
|
|||
description?: Prisma.SortOrder
|
||||
sortOrder?: Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
groupId?: Prisma.SortOrder
|
||||
}
|
||||
|
||||
export type QuizSetMinOrderByAggregateInput = {
|
||||
|
|
@ -405,6 +428,7 @@ export type QuizSetMinOrderByAggregateInput = {
|
|||
description?: Prisma.SortOrder
|
||||
sortOrder?: Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
groupId?: Prisma.SortOrder
|
||||
}
|
||||
|
||||
export type QuizSetSumOrderByAggregateInput = {
|
||||
|
|
@ -523,12 +547,55 @@ export type QuizSetUpdateOneWithoutShareLinkNestedInput = {
|
|||
update?: Prisma.XOR<Prisma.XOR<Prisma.QuizSetUpdateToOneWithWhereWithoutShareLinkInput, Prisma.QuizSetUpdateWithoutShareLinkInput>, Prisma.QuizSetUncheckedUpdateWithoutShareLinkInput>
|
||||
}
|
||||
|
||||
export type QuizSetCreateNestedManyWithoutGroupInput = {
|
||||
create?: Prisma.XOR<Prisma.QuizSetCreateWithoutGroupInput, Prisma.QuizSetUncheckedCreateWithoutGroupInput> | Prisma.QuizSetCreateWithoutGroupInput[] | Prisma.QuizSetUncheckedCreateWithoutGroupInput[]
|
||||
connectOrCreate?: Prisma.QuizSetCreateOrConnectWithoutGroupInput | Prisma.QuizSetCreateOrConnectWithoutGroupInput[]
|
||||
createMany?: Prisma.QuizSetCreateManyGroupInputEnvelope
|
||||
connect?: Prisma.QuizSetWhereUniqueInput | Prisma.QuizSetWhereUniqueInput[]
|
||||
}
|
||||
|
||||
export type QuizSetUncheckedCreateNestedManyWithoutGroupInput = {
|
||||
create?: Prisma.XOR<Prisma.QuizSetCreateWithoutGroupInput, Prisma.QuizSetUncheckedCreateWithoutGroupInput> | Prisma.QuizSetCreateWithoutGroupInput[] | Prisma.QuizSetUncheckedCreateWithoutGroupInput[]
|
||||
connectOrCreate?: Prisma.QuizSetCreateOrConnectWithoutGroupInput | Prisma.QuizSetCreateOrConnectWithoutGroupInput[]
|
||||
createMany?: Prisma.QuizSetCreateManyGroupInputEnvelope
|
||||
connect?: Prisma.QuizSetWhereUniqueInput | Prisma.QuizSetWhereUniqueInput[]
|
||||
}
|
||||
|
||||
export type QuizSetUpdateManyWithoutGroupNestedInput = {
|
||||
create?: Prisma.XOR<Prisma.QuizSetCreateWithoutGroupInput, Prisma.QuizSetUncheckedCreateWithoutGroupInput> | Prisma.QuizSetCreateWithoutGroupInput[] | Prisma.QuizSetUncheckedCreateWithoutGroupInput[]
|
||||
connectOrCreate?: Prisma.QuizSetCreateOrConnectWithoutGroupInput | Prisma.QuizSetCreateOrConnectWithoutGroupInput[]
|
||||
upsert?: Prisma.QuizSetUpsertWithWhereUniqueWithoutGroupInput | Prisma.QuizSetUpsertWithWhereUniqueWithoutGroupInput[]
|
||||
createMany?: Prisma.QuizSetCreateManyGroupInputEnvelope
|
||||
set?: Prisma.QuizSetWhereUniqueInput | Prisma.QuizSetWhereUniqueInput[]
|
||||
disconnect?: Prisma.QuizSetWhereUniqueInput | Prisma.QuizSetWhereUniqueInput[]
|
||||
delete?: Prisma.QuizSetWhereUniqueInput | Prisma.QuizSetWhereUniqueInput[]
|
||||
connect?: Prisma.QuizSetWhereUniqueInput | Prisma.QuizSetWhereUniqueInput[]
|
||||
update?: Prisma.QuizSetUpdateWithWhereUniqueWithoutGroupInput | Prisma.QuizSetUpdateWithWhereUniqueWithoutGroupInput[]
|
||||
updateMany?: Prisma.QuizSetUpdateManyWithWhereWithoutGroupInput | Prisma.QuizSetUpdateManyWithWhereWithoutGroupInput[]
|
||||
deleteMany?: Prisma.QuizSetScalarWhereInput | Prisma.QuizSetScalarWhereInput[]
|
||||
}
|
||||
|
||||
export type QuizSetUncheckedUpdateManyWithoutGroupNestedInput = {
|
||||
create?: Prisma.XOR<Prisma.QuizSetCreateWithoutGroupInput, Prisma.QuizSetUncheckedCreateWithoutGroupInput> | Prisma.QuizSetCreateWithoutGroupInput[] | Prisma.QuizSetUncheckedCreateWithoutGroupInput[]
|
||||
connectOrCreate?: Prisma.QuizSetCreateOrConnectWithoutGroupInput | Prisma.QuizSetCreateOrConnectWithoutGroupInput[]
|
||||
upsert?: Prisma.QuizSetUpsertWithWhereUniqueWithoutGroupInput | Prisma.QuizSetUpsertWithWhereUniqueWithoutGroupInput[]
|
||||
createMany?: Prisma.QuizSetCreateManyGroupInputEnvelope
|
||||
set?: Prisma.QuizSetWhereUniqueInput | Prisma.QuizSetWhereUniqueInput[]
|
||||
disconnect?: Prisma.QuizSetWhereUniqueInput | Prisma.QuizSetWhereUniqueInput[]
|
||||
delete?: Prisma.QuizSetWhereUniqueInput | Prisma.QuizSetWhereUniqueInput[]
|
||||
connect?: Prisma.QuizSetWhereUniqueInput | Prisma.QuizSetWhereUniqueInput[]
|
||||
update?: Prisma.QuizSetUpdateWithWhereUniqueWithoutGroupInput | Prisma.QuizSetUpdateWithWhereUniqueWithoutGroupInput[]
|
||||
updateMany?: Prisma.QuizSetUpdateManyWithWhereWithoutGroupInput | Prisma.QuizSetUpdateManyWithWhereWithoutGroupInput[]
|
||||
deleteMany?: Prisma.QuizSetScalarWhereInput | Prisma.QuizSetScalarWhereInput[]
|
||||
}
|
||||
|
||||
export type QuizSetCreateWithoutClassInput = {
|
||||
id?: string
|
||||
name: string
|
||||
description?: string | null
|
||||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
group?: Prisma.MaterialGroupCreateNestedOneWithoutQuizSetsInput
|
||||
questions?: Prisma.QuestionCreateNestedManyWithoutQuizSetInput
|
||||
attempts?: Prisma.QuizAttemptCreateNestedManyWithoutQuizSetInput
|
||||
shareLink?: Prisma.ShareLinkCreateNestedOneWithoutQuizSetInput
|
||||
|
|
@ -541,6 +608,7 @@ export type QuizSetUncheckedCreateWithoutClassInput = {
|
|||
description?: string | null
|
||||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
groupId?: string | null
|
||||
questions?: Prisma.QuestionUncheckedCreateNestedManyWithoutQuizSetInput
|
||||
attempts?: Prisma.QuizAttemptUncheckedCreateNestedManyWithoutQuizSetInput
|
||||
shareLink?: Prisma.ShareLinkUncheckedCreateNestedOneWithoutQuizSetInput
|
||||
|
|
@ -582,6 +650,7 @@ export type QuizSetScalarWhereInput = {
|
|||
description?: Prisma.StringNullableFilter<"QuizSet"> | string | null
|
||||
sortOrder?: Prisma.IntFilter<"QuizSet"> | number
|
||||
createdAt?: Prisma.DateTimeFilter<"QuizSet"> | Date | string
|
||||
groupId?: Prisma.StringNullableFilter<"QuizSet"> | string | null
|
||||
}
|
||||
|
||||
export type QuizSetCreateWithoutQuestionsInput = {
|
||||
|
|
@ -591,6 +660,7 @@ export type QuizSetCreateWithoutQuestionsInput = {
|
|||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
class: Prisma.ClassCreateNestedOneWithoutQuizSetsInput
|
||||
group?: Prisma.MaterialGroupCreateNestedOneWithoutQuizSetsInput
|
||||
attempts?: Prisma.QuizAttemptCreateNestedManyWithoutQuizSetInput
|
||||
shareLink?: Prisma.ShareLinkCreateNestedOneWithoutQuizSetInput
|
||||
progress?: Prisma.StudyProgressCreateNestedManyWithoutQuizSetInput
|
||||
|
|
@ -603,6 +673,7 @@ export type QuizSetUncheckedCreateWithoutQuestionsInput = {
|
|||
description?: string | null
|
||||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
groupId?: string | null
|
||||
attempts?: Prisma.QuizAttemptUncheckedCreateNestedManyWithoutQuizSetInput
|
||||
shareLink?: Prisma.ShareLinkUncheckedCreateNestedOneWithoutQuizSetInput
|
||||
progress?: Prisma.StudyProgressUncheckedCreateNestedManyWithoutQuizSetInput
|
||||
|
|
@ -631,6 +702,7 @@ export type QuizSetUpdateWithoutQuestionsInput = {
|
|||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
class?: Prisma.ClassUpdateOneRequiredWithoutQuizSetsNestedInput
|
||||
group?: Prisma.MaterialGroupUpdateOneWithoutQuizSetsNestedInput
|
||||
attempts?: Prisma.QuizAttemptUpdateManyWithoutQuizSetNestedInput
|
||||
shareLink?: Prisma.ShareLinkUpdateOneWithoutQuizSetNestedInput
|
||||
progress?: Prisma.StudyProgressUpdateManyWithoutQuizSetNestedInput
|
||||
|
|
@ -643,6 +715,7 @@ export type QuizSetUncheckedUpdateWithoutQuestionsInput = {
|
|||
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
groupId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
attempts?: Prisma.QuizAttemptUncheckedUpdateManyWithoutQuizSetNestedInput
|
||||
shareLink?: Prisma.ShareLinkUncheckedUpdateOneWithoutQuizSetNestedInput
|
||||
progress?: Prisma.StudyProgressUncheckedUpdateManyWithoutQuizSetNestedInput
|
||||
|
|
@ -655,6 +728,7 @@ export type QuizSetCreateWithoutProgressInput = {
|
|||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
class: Prisma.ClassCreateNestedOneWithoutQuizSetsInput
|
||||
group?: Prisma.MaterialGroupCreateNestedOneWithoutQuizSetsInput
|
||||
questions?: Prisma.QuestionCreateNestedManyWithoutQuizSetInput
|
||||
attempts?: Prisma.QuizAttemptCreateNestedManyWithoutQuizSetInput
|
||||
shareLink?: Prisma.ShareLinkCreateNestedOneWithoutQuizSetInput
|
||||
|
|
@ -667,6 +741,7 @@ export type QuizSetUncheckedCreateWithoutProgressInput = {
|
|||
description?: string | null
|
||||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
groupId?: string | null
|
||||
questions?: Prisma.QuestionUncheckedCreateNestedManyWithoutQuizSetInput
|
||||
attempts?: Prisma.QuizAttemptUncheckedCreateNestedManyWithoutQuizSetInput
|
||||
shareLink?: Prisma.ShareLinkUncheckedCreateNestedOneWithoutQuizSetInput
|
||||
|
|
@ -695,6 +770,7 @@ export type QuizSetUpdateWithoutProgressInput = {
|
|||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
class?: Prisma.ClassUpdateOneRequiredWithoutQuizSetsNestedInput
|
||||
group?: Prisma.MaterialGroupUpdateOneWithoutQuizSetsNestedInput
|
||||
questions?: Prisma.QuestionUpdateManyWithoutQuizSetNestedInput
|
||||
attempts?: Prisma.QuizAttemptUpdateManyWithoutQuizSetNestedInput
|
||||
shareLink?: Prisma.ShareLinkUpdateOneWithoutQuizSetNestedInput
|
||||
|
|
@ -707,6 +783,7 @@ export type QuizSetUncheckedUpdateWithoutProgressInput = {
|
|||
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
groupId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
questions?: Prisma.QuestionUncheckedUpdateManyWithoutQuizSetNestedInput
|
||||
attempts?: Prisma.QuizAttemptUncheckedUpdateManyWithoutQuizSetNestedInput
|
||||
shareLink?: Prisma.ShareLinkUncheckedUpdateOneWithoutQuizSetNestedInput
|
||||
|
|
@ -719,6 +796,7 @@ export type QuizSetCreateWithoutAttemptsInput = {
|
|||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
class: Prisma.ClassCreateNestedOneWithoutQuizSetsInput
|
||||
group?: Prisma.MaterialGroupCreateNestedOneWithoutQuizSetsInput
|
||||
questions?: Prisma.QuestionCreateNestedManyWithoutQuizSetInput
|
||||
shareLink?: Prisma.ShareLinkCreateNestedOneWithoutQuizSetInput
|
||||
progress?: Prisma.StudyProgressCreateNestedManyWithoutQuizSetInput
|
||||
|
|
@ -731,6 +809,7 @@ export type QuizSetUncheckedCreateWithoutAttemptsInput = {
|
|||
description?: string | null
|
||||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
groupId?: string | null
|
||||
questions?: Prisma.QuestionUncheckedCreateNestedManyWithoutQuizSetInput
|
||||
shareLink?: Prisma.ShareLinkUncheckedCreateNestedOneWithoutQuizSetInput
|
||||
progress?: Prisma.StudyProgressUncheckedCreateNestedManyWithoutQuizSetInput
|
||||
|
|
@ -759,6 +838,7 @@ export type QuizSetUpdateWithoutAttemptsInput = {
|
|||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
class?: Prisma.ClassUpdateOneRequiredWithoutQuizSetsNestedInput
|
||||
group?: Prisma.MaterialGroupUpdateOneWithoutQuizSetsNestedInput
|
||||
questions?: Prisma.QuestionUpdateManyWithoutQuizSetNestedInput
|
||||
shareLink?: Prisma.ShareLinkUpdateOneWithoutQuizSetNestedInput
|
||||
progress?: Prisma.StudyProgressUpdateManyWithoutQuizSetNestedInput
|
||||
|
|
@ -771,6 +851,7 @@ export type QuizSetUncheckedUpdateWithoutAttemptsInput = {
|
|||
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
groupId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
questions?: Prisma.QuestionUncheckedUpdateManyWithoutQuizSetNestedInput
|
||||
shareLink?: Prisma.ShareLinkUncheckedUpdateOneWithoutQuizSetNestedInput
|
||||
progress?: Prisma.StudyProgressUncheckedUpdateManyWithoutQuizSetNestedInput
|
||||
|
|
@ -783,6 +864,7 @@ export type QuizSetCreateWithoutShareLinkInput = {
|
|||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
class: Prisma.ClassCreateNestedOneWithoutQuizSetsInput
|
||||
group?: Prisma.MaterialGroupCreateNestedOneWithoutQuizSetsInput
|
||||
questions?: Prisma.QuestionCreateNestedManyWithoutQuizSetInput
|
||||
attempts?: Prisma.QuizAttemptCreateNestedManyWithoutQuizSetInput
|
||||
progress?: Prisma.StudyProgressCreateNestedManyWithoutQuizSetInput
|
||||
|
|
@ -795,6 +877,7 @@ export type QuizSetUncheckedCreateWithoutShareLinkInput = {
|
|||
description?: string | null
|
||||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
groupId?: string | null
|
||||
questions?: Prisma.QuestionUncheckedCreateNestedManyWithoutQuizSetInput
|
||||
attempts?: Prisma.QuizAttemptUncheckedCreateNestedManyWithoutQuizSetInput
|
||||
progress?: Prisma.StudyProgressUncheckedCreateNestedManyWithoutQuizSetInput
|
||||
|
|
@ -823,6 +906,7 @@ export type QuizSetUpdateWithoutShareLinkInput = {
|
|||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
class?: Prisma.ClassUpdateOneRequiredWithoutQuizSetsNestedInput
|
||||
group?: Prisma.MaterialGroupUpdateOneWithoutQuizSetsNestedInput
|
||||
questions?: Prisma.QuestionUpdateManyWithoutQuizSetNestedInput
|
||||
attempts?: Prisma.QuizAttemptUpdateManyWithoutQuizSetNestedInput
|
||||
progress?: Prisma.StudyProgressUpdateManyWithoutQuizSetNestedInput
|
||||
|
|
@ -835,17 +919,70 @@ export type QuizSetUncheckedUpdateWithoutShareLinkInput = {
|
|||
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
groupId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
questions?: Prisma.QuestionUncheckedUpdateManyWithoutQuizSetNestedInput
|
||||
attempts?: Prisma.QuizAttemptUncheckedUpdateManyWithoutQuizSetNestedInput
|
||||
progress?: Prisma.StudyProgressUncheckedUpdateManyWithoutQuizSetNestedInput
|
||||
}
|
||||
|
||||
export type QuizSetCreateWithoutGroupInput = {
|
||||
id?: string
|
||||
name: string
|
||||
description?: string | null
|
||||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
class: Prisma.ClassCreateNestedOneWithoutQuizSetsInput
|
||||
questions?: Prisma.QuestionCreateNestedManyWithoutQuizSetInput
|
||||
attempts?: Prisma.QuizAttemptCreateNestedManyWithoutQuizSetInput
|
||||
shareLink?: Prisma.ShareLinkCreateNestedOneWithoutQuizSetInput
|
||||
progress?: Prisma.StudyProgressCreateNestedManyWithoutQuizSetInput
|
||||
}
|
||||
|
||||
export type QuizSetUncheckedCreateWithoutGroupInput = {
|
||||
id?: string
|
||||
classId: string
|
||||
name: string
|
||||
description?: string | null
|
||||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
questions?: Prisma.QuestionUncheckedCreateNestedManyWithoutQuizSetInput
|
||||
attempts?: Prisma.QuizAttemptUncheckedCreateNestedManyWithoutQuizSetInput
|
||||
shareLink?: Prisma.ShareLinkUncheckedCreateNestedOneWithoutQuizSetInput
|
||||
progress?: Prisma.StudyProgressUncheckedCreateNestedManyWithoutQuizSetInput
|
||||
}
|
||||
|
||||
export type QuizSetCreateOrConnectWithoutGroupInput = {
|
||||
where: Prisma.QuizSetWhereUniqueInput
|
||||
create: Prisma.XOR<Prisma.QuizSetCreateWithoutGroupInput, Prisma.QuizSetUncheckedCreateWithoutGroupInput>
|
||||
}
|
||||
|
||||
export type QuizSetCreateManyGroupInputEnvelope = {
|
||||
data: Prisma.QuizSetCreateManyGroupInput | Prisma.QuizSetCreateManyGroupInput[]
|
||||
}
|
||||
|
||||
export type QuizSetUpsertWithWhereUniqueWithoutGroupInput = {
|
||||
where: Prisma.QuizSetWhereUniqueInput
|
||||
update: Prisma.XOR<Prisma.QuizSetUpdateWithoutGroupInput, Prisma.QuizSetUncheckedUpdateWithoutGroupInput>
|
||||
create: Prisma.XOR<Prisma.QuizSetCreateWithoutGroupInput, Prisma.QuizSetUncheckedCreateWithoutGroupInput>
|
||||
}
|
||||
|
||||
export type QuizSetUpdateWithWhereUniqueWithoutGroupInput = {
|
||||
where: Prisma.QuizSetWhereUniqueInput
|
||||
data: Prisma.XOR<Prisma.QuizSetUpdateWithoutGroupInput, Prisma.QuizSetUncheckedUpdateWithoutGroupInput>
|
||||
}
|
||||
|
||||
export type QuizSetUpdateManyWithWhereWithoutGroupInput = {
|
||||
where: Prisma.QuizSetScalarWhereInput
|
||||
data: Prisma.XOR<Prisma.QuizSetUpdateManyMutationInput, Prisma.QuizSetUncheckedUpdateManyWithoutGroupInput>
|
||||
}
|
||||
|
||||
export type QuizSetCreateManyClassInput = {
|
||||
id?: string
|
||||
name: string
|
||||
description?: string | null
|
||||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
groupId?: string | null
|
||||
}
|
||||
|
||||
export type QuizSetUpdateWithoutClassInput = {
|
||||
|
|
@ -854,6 +991,7 @@ export type QuizSetUpdateWithoutClassInput = {
|
|||
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
group?: Prisma.MaterialGroupUpdateOneWithoutQuizSetsNestedInput
|
||||
questions?: Prisma.QuestionUpdateManyWithoutQuizSetNestedInput
|
||||
attempts?: Prisma.QuizAttemptUpdateManyWithoutQuizSetNestedInput
|
||||
shareLink?: Prisma.ShareLinkUpdateOneWithoutQuizSetNestedInput
|
||||
|
|
@ -866,6 +1004,7 @@ export type QuizSetUncheckedUpdateWithoutClassInput = {
|
|||
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
groupId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
questions?: Prisma.QuestionUncheckedUpdateManyWithoutQuizSetNestedInput
|
||||
attempts?: Prisma.QuizAttemptUncheckedUpdateManyWithoutQuizSetNestedInput
|
||||
shareLink?: Prisma.ShareLinkUncheckedUpdateOneWithoutQuizSetNestedInput
|
||||
|
|
@ -878,6 +1017,51 @@ export type QuizSetUncheckedUpdateManyWithoutClassInput = {
|
|||
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
groupId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
}
|
||||
|
||||
export type QuizSetCreateManyGroupInput = {
|
||||
id?: string
|
||||
classId: string
|
||||
name: string
|
||||
description?: string | null
|
||||
sortOrder?: number
|
||||
createdAt?: Date | string
|
||||
}
|
||||
|
||||
export type QuizSetUpdateWithoutGroupInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
class?: Prisma.ClassUpdateOneRequiredWithoutQuizSetsNestedInput
|
||||
questions?: Prisma.QuestionUpdateManyWithoutQuizSetNestedInput
|
||||
attempts?: Prisma.QuizAttemptUpdateManyWithoutQuizSetNestedInput
|
||||
shareLink?: Prisma.ShareLinkUpdateOneWithoutQuizSetNestedInput
|
||||
progress?: Prisma.StudyProgressUpdateManyWithoutQuizSetNestedInput
|
||||
}
|
||||
|
||||
export type QuizSetUncheckedUpdateWithoutGroupInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
classId?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
questions?: Prisma.QuestionUncheckedUpdateManyWithoutQuizSetNestedInput
|
||||
attempts?: Prisma.QuizAttemptUncheckedUpdateManyWithoutQuizSetNestedInput
|
||||
shareLink?: Prisma.ShareLinkUncheckedUpdateOneWithoutQuizSetNestedInput
|
||||
progress?: Prisma.StudyProgressUncheckedUpdateManyWithoutQuizSetNestedInput
|
||||
}
|
||||
|
||||
export type QuizSetUncheckedUpdateManyWithoutGroupInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
classId?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -936,7 +1120,9 @@ export type QuizSetSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs
|
|||
description?: boolean
|
||||
sortOrder?: boolean
|
||||
createdAt?: boolean
|
||||
groupId?: boolean
|
||||
class?: boolean | Prisma.ClassDefaultArgs<ExtArgs>
|
||||
group?: boolean | Prisma.QuizSet$groupArgs<ExtArgs>
|
||||
questions?: boolean | Prisma.QuizSet$questionsArgs<ExtArgs>
|
||||
attempts?: boolean | Prisma.QuizSet$attemptsArgs<ExtArgs>
|
||||
shareLink?: boolean | Prisma.QuizSet$shareLinkArgs<ExtArgs>
|
||||
|
|
@ -951,7 +1137,9 @@ export type QuizSetSelectCreateManyAndReturn<ExtArgs extends runtime.Types.Exten
|
|||
description?: boolean
|
||||
sortOrder?: boolean
|
||||
createdAt?: boolean
|
||||
groupId?: boolean
|
||||
class?: boolean | Prisma.ClassDefaultArgs<ExtArgs>
|
||||
group?: boolean | Prisma.QuizSet$groupArgs<ExtArgs>
|
||||
}, ExtArgs["result"]["quizSet"]>
|
||||
|
||||
export type QuizSetSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
||||
|
|
@ -961,7 +1149,9 @@ export type QuizSetSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.Exten
|
|||
description?: boolean
|
||||
sortOrder?: boolean
|
||||
createdAt?: boolean
|
||||
groupId?: boolean
|
||||
class?: boolean | Prisma.ClassDefaultArgs<ExtArgs>
|
||||
group?: boolean | Prisma.QuizSet$groupArgs<ExtArgs>
|
||||
}, ExtArgs["result"]["quizSet"]>
|
||||
|
||||
export type QuizSetSelectScalar = {
|
||||
|
|
@ -971,11 +1161,13 @@ export type QuizSetSelectScalar = {
|
|||
description?: boolean
|
||||
sortOrder?: boolean
|
||||
createdAt?: boolean
|
||||
groupId?: boolean
|
||||
}
|
||||
|
||||
export type QuizSetOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "classId" | "name" | "description" | "sortOrder" | "createdAt", ExtArgs["result"]["quizSet"]>
|
||||
export type QuizSetOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "classId" | "name" | "description" | "sortOrder" | "createdAt" | "groupId", ExtArgs["result"]["quizSet"]>
|
||||
export type QuizSetInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
class?: boolean | Prisma.ClassDefaultArgs<ExtArgs>
|
||||
group?: boolean | Prisma.QuizSet$groupArgs<ExtArgs>
|
||||
questions?: boolean | Prisma.QuizSet$questionsArgs<ExtArgs>
|
||||
attempts?: boolean | Prisma.QuizSet$attemptsArgs<ExtArgs>
|
||||
shareLink?: boolean | Prisma.QuizSet$shareLinkArgs<ExtArgs>
|
||||
|
|
@ -984,15 +1176,18 @@ export type QuizSetInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs
|
|||
}
|
||||
export type QuizSetIncludeCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
class?: boolean | Prisma.ClassDefaultArgs<ExtArgs>
|
||||
group?: boolean | Prisma.QuizSet$groupArgs<ExtArgs>
|
||||
}
|
||||
export type QuizSetIncludeUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
class?: boolean | Prisma.ClassDefaultArgs<ExtArgs>
|
||||
group?: boolean | Prisma.QuizSet$groupArgs<ExtArgs>
|
||||
}
|
||||
|
||||
export type $QuizSetPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
name: "QuizSet"
|
||||
objects: {
|
||||
class: Prisma.$ClassPayload<ExtArgs>
|
||||
group: Prisma.$MaterialGroupPayload<ExtArgs> | null
|
||||
questions: Prisma.$QuestionPayload<ExtArgs>[]
|
||||
attempts: Prisma.$QuizAttemptPayload<ExtArgs>[]
|
||||
shareLink: Prisma.$ShareLinkPayload<ExtArgs> | null
|
||||
|
|
@ -1005,6 +1200,7 @@ export type $QuizSetPayload<ExtArgs extends runtime.Types.Extensions.InternalArg
|
|||
description: string | null
|
||||
sortOrder: number
|
||||
createdAt: Date
|
||||
groupId: string | null
|
||||
}, ExtArgs["result"]["quizSet"]>
|
||||
composites: {}
|
||||
}
|
||||
|
|
@ -1400,6 +1596,7 @@ readonly fields: QuizSetFieldRefs;
|
|||
export interface Prisma__QuizSetClient<T, Null = never, ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs, GlobalOmitOptions = {}> extends Prisma.PrismaPromise<T> {
|
||||
readonly [Symbol.toStringTag]: "PrismaPromise"
|
||||
class<T extends Prisma.ClassDefaultArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.ClassDefaultArgs<ExtArgs>>): Prisma.Prisma__ClassClient<runtime.Types.Result.GetResult<Prisma.$ClassPayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions>
|
||||
group<T extends Prisma.QuizSet$groupArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.QuizSet$groupArgs<ExtArgs>>): Prisma.Prisma__MaterialGroupClient<runtime.Types.Result.GetResult<Prisma.$MaterialGroupPayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions>
|
||||
questions<T extends Prisma.QuizSet$questionsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.QuizSet$questionsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$QuestionPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||
attempts<T extends Prisma.QuizSet$attemptsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.QuizSet$attemptsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$QuizAttemptPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||
shareLink<T extends Prisma.QuizSet$shareLinkArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.QuizSet$shareLinkArgs<ExtArgs>>): Prisma.Prisma__ShareLinkClient<runtime.Types.Result.GetResult<Prisma.$ShareLinkPayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions>
|
||||
|
|
@ -1439,6 +1636,7 @@ export interface QuizSetFieldRefs {
|
|||
readonly description: Prisma.FieldRef<"QuizSet", 'String'>
|
||||
readonly sortOrder: Prisma.FieldRef<"QuizSet", 'Int'>
|
||||
readonly createdAt: Prisma.FieldRef<"QuizSet", 'DateTime'>
|
||||
readonly groupId: Prisma.FieldRef<"QuizSet", 'String'>
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1837,6 +2035,25 @@ export type QuizSetDeleteManyArgs<ExtArgs extends runtime.Types.Extensions.Inter
|
|||
limit?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* QuizSet.group
|
||||
*/
|
||||
export type QuizSet$groupArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
/**
|
||||
* Select specific fields to fetch from the MaterialGroup
|
||||
*/
|
||||
select?: Prisma.MaterialGroupSelect<ExtArgs> | null
|
||||
/**
|
||||
* Omit specific fields from the MaterialGroup
|
||||
*/
|
||||
omit?: Prisma.MaterialGroupOmit<ExtArgs> | null
|
||||
/**
|
||||
* Choose, which related nodes to fetch as well
|
||||
*/
|
||||
include?: Prisma.MaterialGroupInclude<ExtArgs> | null
|
||||
where?: Prisma.MaterialGroupWhereInput
|
||||
}
|
||||
|
||||
/**
|
||||
* QuizSet.questions
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ export type ShareLinkMinAggregateOutputType = {
|
|||
targetType: string | null
|
||||
deckId: string | null
|
||||
quizSetId: string | null
|
||||
groupId: string | null
|
||||
createdAt: Date | null
|
||||
}
|
||||
|
||||
|
|
@ -37,6 +38,7 @@ export type ShareLinkMaxAggregateOutputType = {
|
|||
targetType: string | null
|
||||
deckId: string | null
|
||||
quizSetId: string | null
|
||||
groupId: string | null
|
||||
createdAt: Date | null
|
||||
}
|
||||
|
||||
|
|
@ -45,6 +47,7 @@ export type ShareLinkCountAggregateOutputType = {
|
|||
targetType: number
|
||||
deckId: number
|
||||
quizSetId: number
|
||||
groupId: number
|
||||
createdAt: number
|
||||
_all: number
|
||||
}
|
||||
|
|
@ -55,6 +58,7 @@ export type ShareLinkMinAggregateInputType = {
|
|||
targetType?: true
|
||||
deckId?: true
|
||||
quizSetId?: true
|
||||
groupId?: true
|
||||
createdAt?: true
|
||||
}
|
||||
|
||||
|
|
@ -63,6 +67,7 @@ export type ShareLinkMaxAggregateInputType = {
|
|||
targetType?: true
|
||||
deckId?: true
|
||||
quizSetId?: true
|
||||
groupId?: true
|
||||
createdAt?: true
|
||||
}
|
||||
|
||||
|
|
@ -71,6 +76,7 @@ export type ShareLinkCountAggregateInputType = {
|
|||
targetType?: true
|
||||
deckId?: true
|
||||
quizSetId?: true
|
||||
groupId?: true
|
||||
createdAt?: true
|
||||
_all?: true
|
||||
}
|
||||
|
|
@ -152,6 +158,7 @@ export type ShareLinkGroupByOutputType = {
|
|||
targetType: string
|
||||
deckId: string | null
|
||||
quizSetId: string | null
|
||||
groupId: string | null
|
||||
createdAt: Date
|
||||
_count: ShareLinkCountAggregateOutputType | null
|
||||
_min: ShareLinkMinAggregateOutputType | null
|
||||
|
|
@ -181,9 +188,11 @@ export type ShareLinkWhereInput = {
|
|||
targetType?: Prisma.StringFilter<"ShareLink"> | string
|
||||
deckId?: Prisma.StringNullableFilter<"ShareLink"> | string | null
|
||||
quizSetId?: Prisma.StringNullableFilter<"ShareLink"> | string | null
|
||||
groupId?: Prisma.StringNullableFilter<"ShareLink"> | string | null
|
||||
createdAt?: Prisma.DateTimeFilter<"ShareLink"> | Date | string
|
||||
deck?: Prisma.XOR<Prisma.DeckNullableScalarRelationFilter, Prisma.DeckWhereInput> | null
|
||||
quizSet?: Prisma.XOR<Prisma.QuizSetNullableScalarRelationFilter, Prisma.QuizSetWhereInput> | null
|
||||
group?: Prisma.XOR<Prisma.MaterialGroupNullableScalarRelationFilter, Prisma.MaterialGroupWhereInput> | null
|
||||
}
|
||||
|
||||
export type ShareLinkOrderByWithRelationInput = {
|
||||
|
|
@ -191,15 +200,18 @@ export type ShareLinkOrderByWithRelationInput = {
|
|||
targetType?: Prisma.SortOrder
|
||||
deckId?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
quizSetId?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
groupId?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
deck?: Prisma.DeckOrderByWithRelationInput
|
||||
quizSet?: Prisma.QuizSetOrderByWithRelationInput
|
||||
group?: Prisma.MaterialGroupOrderByWithRelationInput
|
||||
}
|
||||
|
||||
export type ShareLinkWhereUniqueInput = Prisma.AtLeast<{
|
||||
id?: string
|
||||
deckId?: string
|
||||
quizSetId?: string
|
||||
groupId?: string
|
||||
AND?: Prisma.ShareLinkWhereInput | Prisma.ShareLinkWhereInput[]
|
||||
OR?: Prisma.ShareLinkWhereInput[]
|
||||
NOT?: Prisma.ShareLinkWhereInput | Prisma.ShareLinkWhereInput[]
|
||||
|
|
@ -207,13 +219,15 @@ export type ShareLinkWhereUniqueInput = Prisma.AtLeast<{
|
|||
createdAt?: Prisma.DateTimeFilter<"ShareLink"> | Date | string
|
||||
deck?: Prisma.XOR<Prisma.DeckNullableScalarRelationFilter, Prisma.DeckWhereInput> | null
|
||||
quizSet?: Prisma.XOR<Prisma.QuizSetNullableScalarRelationFilter, Prisma.QuizSetWhereInput> | null
|
||||
}, "id" | "deckId" | "quizSetId">
|
||||
group?: Prisma.XOR<Prisma.MaterialGroupNullableScalarRelationFilter, Prisma.MaterialGroupWhereInput> | null
|
||||
}, "id" | "deckId" | "quizSetId" | "groupId">
|
||||
|
||||
export type ShareLinkOrderByWithAggregationInput = {
|
||||
id?: Prisma.SortOrder
|
||||
targetType?: Prisma.SortOrder
|
||||
deckId?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
quizSetId?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
groupId?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
_count?: Prisma.ShareLinkCountOrderByAggregateInput
|
||||
_max?: Prisma.ShareLinkMaxOrderByAggregateInput
|
||||
|
|
@ -228,6 +242,7 @@ export type ShareLinkScalarWhereWithAggregatesInput = {
|
|||
targetType?: Prisma.StringWithAggregatesFilter<"ShareLink"> | string
|
||||
deckId?: Prisma.StringNullableWithAggregatesFilter<"ShareLink"> | string | null
|
||||
quizSetId?: Prisma.StringNullableWithAggregatesFilter<"ShareLink"> | string | null
|
||||
groupId?: Prisma.StringNullableWithAggregatesFilter<"ShareLink"> | string | null
|
||||
createdAt?: Prisma.DateTimeWithAggregatesFilter<"ShareLink"> | Date | string
|
||||
}
|
||||
|
||||
|
|
@ -237,6 +252,7 @@ export type ShareLinkCreateInput = {
|
|||
createdAt?: Date | string
|
||||
deck?: Prisma.DeckCreateNestedOneWithoutShareLinkInput
|
||||
quizSet?: Prisma.QuizSetCreateNestedOneWithoutShareLinkInput
|
||||
group?: Prisma.MaterialGroupCreateNestedOneWithoutShareLinkInput
|
||||
}
|
||||
|
||||
export type ShareLinkUncheckedCreateInput = {
|
||||
|
|
@ -244,6 +260,7 @@ export type ShareLinkUncheckedCreateInput = {
|
|||
targetType: string
|
||||
deckId?: string | null
|
||||
quizSetId?: string | null
|
||||
groupId?: string | null
|
||||
createdAt?: Date | string
|
||||
}
|
||||
|
||||
|
|
@ -253,6 +270,7 @@ export type ShareLinkUpdateInput = {
|
|||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
deck?: Prisma.DeckUpdateOneWithoutShareLinkNestedInput
|
||||
quizSet?: Prisma.QuizSetUpdateOneWithoutShareLinkNestedInput
|
||||
group?: Prisma.MaterialGroupUpdateOneWithoutShareLinkNestedInput
|
||||
}
|
||||
|
||||
export type ShareLinkUncheckedUpdateInput = {
|
||||
|
|
@ -260,6 +278,7 @@ export type ShareLinkUncheckedUpdateInput = {
|
|||
targetType?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
deckId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
quizSetId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
groupId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
}
|
||||
|
||||
|
|
@ -268,6 +287,7 @@ export type ShareLinkCreateManyInput = {
|
|||
targetType: string
|
||||
deckId?: string | null
|
||||
quizSetId?: string | null
|
||||
groupId?: string | null
|
||||
createdAt?: Date | string
|
||||
}
|
||||
|
||||
|
|
@ -282,6 +302,7 @@ export type ShareLinkUncheckedUpdateManyInput = {
|
|||
targetType?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
deckId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
quizSetId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
groupId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
}
|
||||
|
||||
|
|
@ -295,6 +316,7 @@ export type ShareLinkCountOrderByAggregateInput = {
|
|||
targetType?: Prisma.SortOrder
|
||||
deckId?: Prisma.SortOrder
|
||||
quizSetId?: Prisma.SortOrder
|
||||
groupId?: Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
}
|
||||
|
||||
|
|
@ -303,6 +325,7 @@ export type ShareLinkMaxOrderByAggregateInput = {
|
|||
targetType?: Prisma.SortOrder
|
||||
deckId?: Prisma.SortOrder
|
||||
quizSetId?: Prisma.SortOrder
|
||||
groupId?: Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
}
|
||||
|
||||
|
|
@ -311,6 +334,7 @@ export type ShareLinkMinOrderByAggregateInput = {
|
|||
targetType?: Prisma.SortOrder
|
||||
deckId?: Prisma.SortOrder
|
||||
quizSetId?: Prisma.SortOrder
|
||||
groupId?: Prisma.SortOrder
|
||||
createdAt?: Prisma.SortOrder
|
||||
}
|
||||
|
||||
|
|
@ -378,17 +402,51 @@ export type ShareLinkUncheckedUpdateOneWithoutQuizSetNestedInput = {
|
|||
update?: Prisma.XOR<Prisma.XOR<Prisma.ShareLinkUpdateToOneWithWhereWithoutQuizSetInput, Prisma.ShareLinkUpdateWithoutQuizSetInput>, Prisma.ShareLinkUncheckedUpdateWithoutQuizSetInput>
|
||||
}
|
||||
|
||||
export type ShareLinkCreateNestedOneWithoutGroupInput = {
|
||||
create?: Prisma.XOR<Prisma.ShareLinkCreateWithoutGroupInput, Prisma.ShareLinkUncheckedCreateWithoutGroupInput>
|
||||
connectOrCreate?: Prisma.ShareLinkCreateOrConnectWithoutGroupInput
|
||||
connect?: Prisma.ShareLinkWhereUniqueInput
|
||||
}
|
||||
|
||||
export type ShareLinkUncheckedCreateNestedOneWithoutGroupInput = {
|
||||
create?: Prisma.XOR<Prisma.ShareLinkCreateWithoutGroupInput, Prisma.ShareLinkUncheckedCreateWithoutGroupInput>
|
||||
connectOrCreate?: Prisma.ShareLinkCreateOrConnectWithoutGroupInput
|
||||
connect?: Prisma.ShareLinkWhereUniqueInput
|
||||
}
|
||||
|
||||
export type ShareLinkUpdateOneWithoutGroupNestedInput = {
|
||||
create?: Prisma.XOR<Prisma.ShareLinkCreateWithoutGroupInput, Prisma.ShareLinkUncheckedCreateWithoutGroupInput>
|
||||
connectOrCreate?: Prisma.ShareLinkCreateOrConnectWithoutGroupInput
|
||||
upsert?: Prisma.ShareLinkUpsertWithoutGroupInput
|
||||
disconnect?: Prisma.ShareLinkWhereInput | boolean
|
||||
delete?: Prisma.ShareLinkWhereInput | boolean
|
||||
connect?: Prisma.ShareLinkWhereUniqueInput
|
||||
update?: Prisma.XOR<Prisma.XOR<Prisma.ShareLinkUpdateToOneWithWhereWithoutGroupInput, Prisma.ShareLinkUpdateWithoutGroupInput>, Prisma.ShareLinkUncheckedUpdateWithoutGroupInput>
|
||||
}
|
||||
|
||||
export type ShareLinkUncheckedUpdateOneWithoutGroupNestedInput = {
|
||||
create?: Prisma.XOR<Prisma.ShareLinkCreateWithoutGroupInput, Prisma.ShareLinkUncheckedCreateWithoutGroupInput>
|
||||
connectOrCreate?: Prisma.ShareLinkCreateOrConnectWithoutGroupInput
|
||||
upsert?: Prisma.ShareLinkUpsertWithoutGroupInput
|
||||
disconnect?: Prisma.ShareLinkWhereInput | boolean
|
||||
delete?: Prisma.ShareLinkWhereInput | boolean
|
||||
connect?: Prisma.ShareLinkWhereUniqueInput
|
||||
update?: Prisma.XOR<Prisma.XOR<Prisma.ShareLinkUpdateToOneWithWhereWithoutGroupInput, Prisma.ShareLinkUpdateWithoutGroupInput>, Prisma.ShareLinkUncheckedUpdateWithoutGroupInput>
|
||||
}
|
||||
|
||||
export type ShareLinkCreateWithoutDeckInput = {
|
||||
id?: string
|
||||
targetType: string
|
||||
createdAt?: Date | string
|
||||
quizSet?: Prisma.QuizSetCreateNestedOneWithoutShareLinkInput
|
||||
group?: Prisma.MaterialGroupCreateNestedOneWithoutShareLinkInput
|
||||
}
|
||||
|
||||
export type ShareLinkUncheckedCreateWithoutDeckInput = {
|
||||
id?: string
|
||||
targetType: string
|
||||
quizSetId?: string | null
|
||||
groupId?: string | null
|
||||
createdAt?: Date | string
|
||||
}
|
||||
|
||||
|
|
@ -413,12 +471,14 @@ export type ShareLinkUpdateWithoutDeckInput = {
|
|||
targetType?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
quizSet?: Prisma.QuizSetUpdateOneWithoutShareLinkNestedInput
|
||||
group?: Prisma.MaterialGroupUpdateOneWithoutShareLinkNestedInput
|
||||
}
|
||||
|
||||
export type ShareLinkUncheckedUpdateWithoutDeckInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
targetType?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
quizSetId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
groupId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
}
|
||||
|
||||
|
|
@ -427,12 +487,14 @@ export type ShareLinkCreateWithoutQuizSetInput = {
|
|||
targetType: string
|
||||
createdAt?: Date | string
|
||||
deck?: Prisma.DeckCreateNestedOneWithoutShareLinkInput
|
||||
group?: Prisma.MaterialGroupCreateNestedOneWithoutShareLinkInput
|
||||
}
|
||||
|
||||
export type ShareLinkUncheckedCreateWithoutQuizSetInput = {
|
||||
id?: string
|
||||
targetType: string
|
||||
deckId?: string | null
|
||||
groupId?: string | null
|
||||
createdAt?: Date | string
|
||||
}
|
||||
|
||||
|
|
@ -457,12 +519,62 @@ export type ShareLinkUpdateWithoutQuizSetInput = {
|
|||
targetType?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
deck?: Prisma.DeckUpdateOneWithoutShareLinkNestedInput
|
||||
group?: Prisma.MaterialGroupUpdateOneWithoutShareLinkNestedInput
|
||||
}
|
||||
|
||||
export type ShareLinkUncheckedUpdateWithoutQuizSetInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
targetType?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
deckId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
groupId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
}
|
||||
|
||||
export type ShareLinkCreateWithoutGroupInput = {
|
||||
id?: string
|
||||
targetType: string
|
||||
createdAt?: Date | string
|
||||
deck?: Prisma.DeckCreateNestedOneWithoutShareLinkInput
|
||||
quizSet?: Prisma.QuizSetCreateNestedOneWithoutShareLinkInput
|
||||
}
|
||||
|
||||
export type ShareLinkUncheckedCreateWithoutGroupInput = {
|
||||
id?: string
|
||||
targetType: string
|
||||
deckId?: string | null
|
||||
quizSetId?: string | null
|
||||
createdAt?: Date | string
|
||||
}
|
||||
|
||||
export type ShareLinkCreateOrConnectWithoutGroupInput = {
|
||||
where: Prisma.ShareLinkWhereUniqueInput
|
||||
create: Prisma.XOR<Prisma.ShareLinkCreateWithoutGroupInput, Prisma.ShareLinkUncheckedCreateWithoutGroupInput>
|
||||
}
|
||||
|
||||
export type ShareLinkUpsertWithoutGroupInput = {
|
||||
update: Prisma.XOR<Prisma.ShareLinkUpdateWithoutGroupInput, Prisma.ShareLinkUncheckedUpdateWithoutGroupInput>
|
||||
create: Prisma.XOR<Prisma.ShareLinkCreateWithoutGroupInput, Prisma.ShareLinkUncheckedCreateWithoutGroupInput>
|
||||
where?: Prisma.ShareLinkWhereInput
|
||||
}
|
||||
|
||||
export type ShareLinkUpdateToOneWithWhereWithoutGroupInput = {
|
||||
where?: Prisma.ShareLinkWhereInput
|
||||
data: Prisma.XOR<Prisma.ShareLinkUpdateWithoutGroupInput, Prisma.ShareLinkUncheckedUpdateWithoutGroupInput>
|
||||
}
|
||||
|
||||
export type ShareLinkUpdateWithoutGroupInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
targetType?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
deck?: Prisma.DeckUpdateOneWithoutShareLinkNestedInput
|
||||
quizSet?: Prisma.QuizSetUpdateOneWithoutShareLinkNestedInput
|
||||
}
|
||||
|
||||
export type ShareLinkUncheckedUpdateWithoutGroupInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
targetType?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
deckId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
quizSetId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
}
|
||||
|
||||
|
|
@ -473,9 +585,11 @@ export type ShareLinkSelect<ExtArgs extends runtime.Types.Extensions.InternalArg
|
|||
targetType?: boolean
|
||||
deckId?: boolean
|
||||
quizSetId?: boolean
|
||||
groupId?: boolean
|
||||
createdAt?: boolean
|
||||
deck?: boolean | Prisma.ShareLink$deckArgs<ExtArgs>
|
||||
quizSet?: boolean | Prisma.ShareLink$quizSetArgs<ExtArgs>
|
||||
group?: boolean | Prisma.ShareLink$groupArgs<ExtArgs>
|
||||
}, ExtArgs["result"]["shareLink"]>
|
||||
|
||||
export type ShareLinkSelectCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
||||
|
|
@ -483,9 +597,11 @@ export type ShareLinkSelectCreateManyAndReturn<ExtArgs extends runtime.Types.Ext
|
|||
targetType?: boolean
|
||||
deckId?: boolean
|
||||
quizSetId?: boolean
|
||||
groupId?: boolean
|
||||
createdAt?: boolean
|
||||
deck?: boolean | Prisma.ShareLink$deckArgs<ExtArgs>
|
||||
quizSet?: boolean | Prisma.ShareLink$quizSetArgs<ExtArgs>
|
||||
group?: boolean | Prisma.ShareLink$groupArgs<ExtArgs>
|
||||
}, ExtArgs["result"]["shareLink"]>
|
||||
|
||||
export type ShareLinkSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
||||
|
|
@ -493,9 +609,11 @@ export type ShareLinkSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.Ext
|
|||
targetType?: boolean
|
||||
deckId?: boolean
|
||||
quizSetId?: boolean
|
||||
groupId?: boolean
|
||||
createdAt?: boolean
|
||||
deck?: boolean | Prisma.ShareLink$deckArgs<ExtArgs>
|
||||
quizSet?: boolean | Prisma.ShareLink$quizSetArgs<ExtArgs>
|
||||
group?: boolean | Prisma.ShareLink$groupArgs<ExtArgs>
|
||||
}, ExtArgs["result"]["shareLink"]>
|
||||
|
||||
export type ShareLinkSelectScalar = {
|
||||
|
|
@ -503,21 +621,25 @@ export type ShareLinkSelectScalar = {
|
|||
targetType?: boolean
|
||||
deckId?: boolean
|
||||
quizSetId?: boolean
|
||||
groupId?: boolean
|
||||
createdAt?: boolean
|
||||
}
|
||||
|
||||
export type ShareLinkOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "targetType" | "deckId" | "quizSetId" | "createdAt", ExtArgs["result"]["shareLink"]>
|
||||
export type ShareLinkOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "targetType" | "deckId" | "quizSetId" | "groupId" | "createdAt", ExtArgs["result"]["shareLink"]>
|
||||
export type ShareLinkInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
deck?: boolean | Prisma.ShareLink$deckArgs<ExtArgs>
|
||||
quizSet?: boolean | Prisma.ShareLink$quizSetArgs<ExtArgs>
|
||||
group?: boolean | Prisma.ShareLink$groupArgs<ExtArgs>
|
||||
}
|
||||
export type ShareLinkIncludeCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
deck?: boolean | Prisma.ShareLink$deckArgs<ExtArgs>
|
||||
quizSet?: boolean | Prisma.ShareLink$quizSetArgs<ExtArgs>
|
||||
group?: boolean | Prisma.ShareLink$groupArgs<ExtArgs>
|
||||
}
|
||||
export type ShareLinkIncludeUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
deck?: boolean | Prisma.ShareLink$deckArgs<ExtArgs>
|
||||
quizSet?: boolean | Prisma.ShareLink$quizSetArgs<ExtArgs>
|
||||
group?: boolean | Prisma.ShareLink$groupArgs<ExtArgs>
|
||||
}
|
||||
|
||||
export type $ShareLinkPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
|
|
@ -525,12 +647,14 @@ export type $ShareLinkPayload<ExtArgs extends runtime.Types.Extensions.InternalA
|
|||
objects: {
|
||||
deck: Prisma.$DeckPayload<ExtArgs> | null
|
||||
quizSet: Prisma.$QuizSetPayload<ExtArgs> | null
|
||||
group: Prisma.$MaterialGroupPayload<ExtArgs> | null
|
||||
}
|
||||
scalars: runtime.Types.Extensions.GetPayloadResult<{
|
||||
id: string
|
||||
targetType: string
|
||||
deckId: string | null
|
||||
quizSetId: string | null
|
||||
groupId: string | null
|
||||
createdAt: Date
|
||||
}, ExtArgs["result"]["shareLink"]>
|
||||
composites: {}
|
||||
|
|
@ -928,6 +1052,7 @@ export interface Prisma__ShareLinkClient<T, Null = never, ExtArgs extends runtim
|
|||
readonly [Symbol.toStringTag]: "PrismaPromise"
|
||||
deck<T extends Prisma.ShareLink$deckArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.ShareLink$deckArgs<ExtArgs>>): Prisma.Prisma__DeckClient<runtime.Types.Result.GetResult<Prisma.$DeckPayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions>
|
||||
quizSet<T extends Prisma.ShareLink$quizSetArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.ShareLink$quizSetArgs<ExtArgs>>): Prisma.Prisma__QuizSetClient<runtime.Types.Result.GetResult<Prisma.$QuizSetPayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions>
|
||||
group<T extends Prisma.ShareLink$groupArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.ShareLink$groupArgs<ExtArgs>>): Prisma.Prisma__MaterialGroupClient<runtime.Types.Result.GetResult<Prisma.$MaterialGroupPayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions>
|
||||
/**
|
||||
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
||||
* @param onfulfilled The callback to execute when the Promise is resolved.
|
||||
|
|
@ -961,6 +1086,7 @@ export interface ShareLinkFieldRefs {
|
|||
readonly targetType: Prisma.FieldRef<"ShareLink", 'String'>
|
||||
readonly deckId: Prisma.FieldRef<"ShareLink", 'String'>
|
||||
readonly quizSetId: Prisma.FieldRef<"ShareLink", 'String'>
|
||||
readonly groupId: Prisma.FieldRef<"ShareLink", 'String'>
|
||||
readonly createdAt: Prisma.FieldRef<"ShareLink", 'DateTime'>
|
||||
}
|
||||
|
||||
|
|
@ -1398,6 +1524,25 @@ export type ShareLink$quizSetArgs<ExtArgs extends runtime.Types.Extensions.Inter
|
|||
where?: Prisma.QuizSetWhereInput
|
||||
}
|
||||
|
||||
/**
|
||||
* ShareLink.group
|
||||
*/
|
||||
export type ShareLink$groupArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
/**
|
||||
* Select specific fields to fetch from the MaterialGroup
|
||||
*/
|
||||
select?: Prisma.MaterialGroupSelect<ExtArgs> | null
|
||||
/**
|
||||
* Omit specific fields from the MaterialGroup
|
||||
*/
|
||||
omit?: Prisma.MaterialGroupOmit<ExtArgs> | null
|
||||
/**
|
||||
* Choose, which related nodes to fetch as well
|
||||
*/
|
||||
include?: Prisma.MaterialGroupInclude<ExtArgs> | null
|
||||
where?: Prisma.MaterialGroupWhereInput
|
||||
}
|
||||
|
||||
/**
|
||||
* ShareLink without action
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -33,10 +33,11 @@ export async function getDeckWithCards(deckId: string) {
|
|||
export async function createDeckFromImport(
|
||||
classId: string,
|
||||
data: FlashcardImportData,
|
||||
overrideName?: string
|
||||
overrideName?: string,
|
||||
groupId?: string | null
|
||||
) {
|
||||
const maxOrder = await prisma.deck.aggregate({
|
||||
where: { classId },
|
||||
where: { classId, groupId: groupId || null },
|
||||
_max: { sortOrder: true },
|
||||
});
|
||||
const sortOrder = (maxOrder._max.sortOrder ?? -1) + 1;
|
||||
|
|
@ -44,6 +45,7 @@ export async function createDeckFromImport(
|
|||
return prisma.deck.create({
|
||||
data: {
|
||||
classId,
|
||||
groupId: groupId || null,
|
||||
name: overrideName || data.deckName,
|
||||
description: data.description,
|
||||
sortOrder,
|
||||
|
|
|
|||
|
|
@ -38,10 +38,11 @@ export async function getQuizSetWithQuestions(quizSetId: string) {
|
|||
export async function createQuizSetFromImport(
|
||||
classId: string,
|
||||
data: QuizImportData,
|
||||
overrideName?: string
|
||||
overrideName?: string,
|
||||
groupId?: string | null
|
||||
) {
|
||||
const maxOrder = await prisma.quizSet.aggregate({
|
||||
where: { classId },
|
||||
where: { classId, groupId: groupId || null },
|
||||
_max: { sortOrder: true },
|
||||
});
|
||||
const sortOrder = (maxOrder._max.sortOrder ?? -1) + 1;
|
||||
|
|
@ -49,6 +50,7 @@ export async function createQuizSetFromImport(
|
|||
return prisma.quizSet.create({
|
||||
data: {
|
||||
classId,
|
||||
groupId: groupId || null,
|
||||
name: overrideName || data.quizName,
|
||||
description: data.description,
|
||||
sortOrder,
|
||||
|
|
|
|||
|
|
@ -21,18 +21,45 @@ export async function getShareLink(token: string) {
|
|||
class: { select: { slug: true, name: true } },
|
||||
},
|
||||
},
|
||||
group: {
|
||||
include: {
|
||||
class: { select: { slug: true, name: true } },
|
||||
decks: {
|
||||
include: {
|
||||
cards: { orderBy: { sortOrder: "asc" } },
|
||||
class: { select: { slug: true, name: true } },
|
||||
},
|
||||
orderBy: { sortOrder: "asc" },
|
||||
},
|
||||
quizSets: {
|
||||
include: {
|
||||
questions: {
|
||||
orderBy: { sortOrder: "asc" },
|
||||
include: {
|
||||
options: { orderBy: { sortOrder: "asc" } },
|
||||
},
|
||||
},
|
||||
class: { select: { slug: true, name: true } },
|
||||
},
|
||||
orderBy: { sortOrder: "asc" },
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function getShareLinkForContent(targetType: "DECK" | "QUIZ", contentId: string) {
|
||||
export async function getShareLinkForContent(targetType: "DECK" | "QUIZ" | "GROUP", contentId: string) {
|
||||
if (targetType === "DECK") {
|
||||
return prisma.shareLink.findUnique({ where: { deckId: contentId } });
|
||||
}
|
||||
if (targetType === "GROUP") {
|
||||
return prisma.shareLink.findUnique({ where: { groupId: contentId } });
|
||||
}
|
||||
return prisma.shareLink.findUnique({ where: { quizSetId: contentId } });
|
||||
}
|
||||
|
||||
export async function toggleShareLink(targetType: "DECK" | "QUIZ", contentId: string) {
|
||||
export async function toggleShareLink(targetType: "DECK" | "QUIZ" | "GROUP", contentId: string) {
|
||||
const existing = await getShareLinkForContent(targetType, contentId);
|
||||
|
||||
if (existing) {
|
||||
|
|
@ -47,6 +74,28 @@ export async function toggleShareLink(targetType: "DECK" | "QUIZ", contentId: st
|
|||
targetType,
|
||||
deckId: targetType === "DECK" ? contentId : null,
|
||||
quizSetId: targetType === "QUIZ" ? contentId : null,
|
||||
groupId: targetType === "GROUP" ? contentId : null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function isContentSharedViaGroup(targetType: "DECK" | "QUIZ", contentId: string) {
|
||||
let groupId: string | null = null;
|
||||
|
||||
if (targetType === "DECK") {
|
||||
const deck = await prisma.deck.findUnique({ where: { id: contentId }, select: { groupId: true } });
|
||||
groupId = deck?.groupId || null;
|
||||
} else {
|
||||
const quiz = await prisma.quizSet.findUnique({ where: { id: contentId }, select: { groupId: true } });
|
||||
groupId = quiz?.groupId || null;
|
||||
}
|
||||
|
||||
if (!groupId) return null;
|
||||
|
||||
const groupLink = await prisma.shareLink.findUnique({
|
||||
where: { groupId },
|
||||
include: { group: true },
|
||||
});
|
||||
|
||||
return groupLink ? { token: groupLink.id, groupName: groupLink.group?.name } : null;
|
||||
}
|
||||
|
|
|
|||
1
test.css
Normal file
1
test.css
Normal file
|
|
@ -0,0 +1 @@
|
|||
:root { --color-primary: #fff; }
|
||||
Loading…
Add table
Add a link
Reference in a new issue