mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
ready
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
// Column definitions and pinning for the standalone Lesson Library table.
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Clock } from "lucide-react";
|
||||
import { Clock, Layers } from "lucide-react";
|
||||
import { buildColumns } from "@/utils/table.util";
|
||||
import { buildSelectionColumn } from "@/components/generic/Table/buildSelectionColumn";
|
||||
import { buildRowActionsColumn } from "@/components/generic/Table/buildRowActionsColumn";
|
||||
@@ -13,13 +13,23 @@ export const columnPinning = {
|
||||
left: [],
|
||||
};
|
||||
|
||||
const COURSE_STATUS_BADGE = {
|
||||
published: "default",
|
||||
draft: "secondary",
|
||||
standalone: "outline",
|
||||
};
|
||||
|
||||
const cellOverrides = {
|
||||
title: (info) => {
|
||||
const inUnit = parseInt(info.row.original.unit_count ?? 0, 10) > 0;
|
||||
return (
|
||||
<div className="flex items-center gap-1.5 min-w-0">
|
||||
{inUnit && (
|
||||
<Badge variant="secondary" className="gap-1 text-[10px] font-medium shrink-0">
|
||||
<Layers className="h-3 w-3" />
|
||||
Unit
|
||||
</Badge>
|
||||
)}
|
||||
<span className="block truncate max-w-56 text-sm" title={info.getValue()}>
|
||||
{info.getValue()}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
duration_seconds: (info) => {
|
||||
const seconds = parseInt(info.getValue() ?? 0, 10);
|
||||
return (
|
||||
@@ -39,11 +49,6 @@ const cellOverrides = {
|
||||
</Badge>
|
||||
);
|
||||
},
|
||||
course_status: (info) => (
|
||||
<Badge variant={COURSE_STATUS_BADGE[info.getValue()] ?? "outline"} className="text-xs capitalize">
|
||||
{info.getValue()}
|
||||
</Badge>
|
||||
),
|
||||
};
|
||||
|
||||
export function buildDataColumns(attributes, rowActions) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Column definitions and pinning for the standalone Unit Library table.
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Clock } from "lucide-react";
|
||||
import { Clock, GraduationCap } from "lucide-react";
|
||||
import { buildColumns } from "@/utils/table.util";
|
||||
import { buildSelectionColumn } from "@/components/generic/Table/buildSelectionColumn";
|
||||
import { buildRowActionsColumn } from "@/components/generic/Table/buildRowActionsColumn";
|
||||
@@ -13,13 +13,23 @@ export const columnPinning = {
|
||||
left: [],
|
||||
};
|
||||
|
||||
const COURSE_STATUS_BADGE = {
|
||||
published: "default",
|
||||
draft: "secondary",
|
||||
standalone: "outline",
|
||||
};
|
||||
|
||||
const cellOverrides = {
|
||||
title: (info) => {
|
||||
const inCourse = parseInt(info.row.original.course_count ?? 0, 10) > 0;
|
||||
return (
|
||||
<div className="flex items-center gap-1.5 min-w-0">
|
||||
{inCourse && (
|
||||
<Badge variant="secondary" className="gap-1 text-[10px] font-medium shrink-0">
|
||||
<GraduationCap className="h-3 w-3" />
|
||||
Course
|
||||
</Badge>
|
||||
)}
|
||||
<span className="block truncate max-w-56 text-sm" title={info.getValue()}>
|
||||
{info.getValue()}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
duration_seconds: (info) => {
|
||||
const seconds = parseInt(info.getValue() ?? 0, 10);
|
||||
return (
|
||||
@@ -36,19 +46,6 @@ const cellOverrides = {
|
||||
{info.getValue() ?? 0} lesson{(info.getValue() ?? 0) === 1 ? "" : "s"}
|
||||
</Badge>
|
||||
),
|
||||
course_count: (info) => {
|
||||
const n = parseInt(info.getValue() ?? 0, 10);
|
||||
return (
|
||||
<Badge variant="secondary" className="text-xs tabular-nums">
|
||||
{n} course{n === 1 ? "" : "s"}
|
||||
</Badge>
|
||||
);
|
||||
},
|
||||
course_status: (info) => (
|
||||
<Badge variant={COURSE_STATUS_BADGE[info.getValue()] ?? "outline"} className="text-xs capitalize">
|
||||
{info.getValue()}
|
||||
</Badge>
|
||||
),
|
||||
};
|
||||
|
||||
export function buildDataColumns(attributes, rowActions) {
|
||||
|
||||
@@ -14,12 +14,6 @@ export function buildRowActions({ onView, onEdit, onManageLessons, onArchive, on
|
||||
icon: <Eye className="h-3.5 w-3.5" />,
|
||||
onClick: (row) => onView(row),
|
||||
},
|
||||
{
|
||||
key: "edit",
|
||||
label: "Edit Unit",
|
||||
icon: <Pencil className="h-3.5 w-3.5" />,
|
||||
onClick: (row) => onEdit(row),
|
||||
},
|
||||
{
|
||||
key: "manage_lessons",
|
||||
label: "Manage Lessons",
|
||||
|
||||
@@ -185,12 +185,6 @@ export default function AddAsset() {
|
||||
hasFileError = true;
|
||||
}
|
||||
|
||||
// Thumbnail is required for video, optional for audio
|
||||
if (isVideo && !thumbnailRef.current) {
|
||||
setError("_thumbnail", { message: "A thumbnail is required for video uploads." });
|
||||
hasFileError = true;
|
||||
}
|
||||
|
||||
if (hasFileError) return;
|
||||
|
||||
setProgress({ phase: "uploading", pct: 0 });
|
||||
@@ -259,15 +253,14 @@ export default function AddAsset() {
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* ── Thumbnail (video required / audio optional) ── */}
|
||||
{/* ── Thumbnail (optional for both video and audio) ── */}
|
||||
{(isVideo || isAudio) && (
|
||||
<div className="space-y-1.5">
|
||||
<Label>
|
||||
Thumbnail{" "}
|
||||
{isVideo
|
||||
? <span className="text-destructive">*</span>
|
||||
: <span className="text-muted-foreground text-xs">(optional — album / cover art)</span>
|
||||
}
|
||||
<span className="text-muted-foreground text-xs">
|
||||
(optional — {isVideo ? "video preview" : "album / cover art"})
|
||||
</span>
|
||||
</Label>
|
||||
<DropZone
|
||||
key={thumbKey}
|
||||
@@ -350,23 +343,15 @@ export default function AddAsset() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Storage Provider */}
|
||||
{/* Storage Provider — TEMPORARY: locked to S3 (see zrok tunnel note above; revert after VPS migration) */}
|
||||
<div className="space-y-1.5">
|
||||
<Label>Storage</Label>
|
||||
<Controller
|
||||
name="storage_provider"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Select value={field.value} onValueChange={field.onChange}>
|
||||
<SelectTrigger><SelectValue /></SelectTrigger>
|
||||
<Select value="s3" disabled>
|
||||
<SelectTrigger className="disabled:opacity-60 disabled:cursor-not-allowed"><SelectValue /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="chibisafe">Chibisafe</SelectItem>
|
||||
<SelectItem value="local">Local</SelectItem>
|
||||
<SelectItem value="s3">S3</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -131,7 +131,8 @@ export default function AddAssetsBulk() {
|
||||
const { user } = useAuth();
|
||||
|
||||
const [isPublic, setIsPublic] = useState("false");
|
||||
const [storageProvider, setStorageProvider] = useState("s3");
|
||||
// TEMPORARY: locked to S3 (see zrok tunnel note; revert after VPS migration)
|
||||
const storageProvider = "s3";
|
||||
|
||||
const total = jobs.length;
|
||||
const done = jobs.filter((j) => j.status === "uploaded").length;
|
||||
@@ -177,11 +178,10 @@ export default function AddAssetsBulk() {
|
||||
</div>
|
||||
<div className="flex-1 space-y-1.5">
|
||||
<label className="text-sm font-medium">Storage</label>
|
||||
<Select value={storageProvider} onValueChange={setStorageProvider}>
|
||||
<SelectTrigger><SelectValue /></SelectTrigger>
|
||||
{/* TEMPORARY: locked to S3 (see zrok tunnel note; revert after VPS migration) */}
|
||||
<Select value={storageProvider} disabled>
|
||||
<SelectTrigger className="disabled:opacity-60 disabled:cursor-not-allowed"><SelectValue /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="chibisafe">Chibisafe</SelectItem>
|
||||
<SelectItem value="local">Local</SelectItem>
|
||||
<SelectItem value="s3">S3</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
@@ -113,7 +113,7 @@ function InviteLinkDialog({ open, onOpenChange, group }) {
|
||||
{/* Invite link */}
|
||||
<div className="w-full flex flex-col gap-2">
|
||||
<div className="flex items-center gap-2 rounded-lg border bg-muted px-3 py-2 min-w-0">
|
||||
<p className="font-mono text-xs text-muted-foreground truncate flex-1">
|
||||
<p className="font-mono text-xs text-muted-foreground truncate w-64">
|
||||
{inviteUrl}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -112,7 +112,7 @@ function ReferDialog({ open, onOpenChange }) {
|
||||
{/* Invite link */}
|
||||
<div className="w-full flex flex-col gap-2">
|
||||
<div className="flex items-center gap-2 rounded-lg border bg-muted px-3 py-2 min-w-0">
|
||||
<p className="font-mono text-xs text-muted-foreground truncate flex-1">
|
||||
<p className="font-mono text-xs text-muted-foreground truncate w-64">
|
||||
{inviteUrl}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -129,7 +129,7 @@ function renderCell(attr, value) {
|
||||
}
|
||||
case "date": return <span className="text-xs text-muted-foreground">{formatDate(value)}</span>;
|
||||
case "number": return <span className="font-mono text-xs font-semibold text-muted-foreground">{value}</span>;
|
||||
default: return <span className="text-sm">{value}</span>;
|
||||
default: return <span className="block truncate max-w-56 text-sm" title={String(value)}>{value}</span>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user