11"use client" ;
22
3- import Link from "next/link " ;
3+ import type { ReactNode } from "react " ;
44import { useRouter } from "next/navigation" ;
5- import { useTransition } from "react" ;
6- import { FileText , Pencil } from "lucide-react" ;
7- import { createDocumentAction , renameDocumentAction } from "../../lib/actions" ;
5+ import { useState , useTransition } from "react" ;
6+ import { Archive , ArchiveRestore , ChevronDown , FileText , Pencil } from "lucide-react" ;
7+ import {
8+ archiveDocumentAction ,
9+ createDocumentAction ,
10+ renameDocumentAction ,
11+ unarchiveDocumentAction ,
12+ } from "../../lib/actions" ;
813import { runAction } from "./runAction" ;
914import { getDocumentName } from "../../lib/documents" ;
10- import { ResourceCard , NewResourceCard , ActionButton , formatRelativeTime } from "./ResourceCard" ;
15+ import { ResourceCard , NewResourceCard , formatRelativeTime } from "./ResourceCard" ;
16+ import { Button } from "@/components/ui/button" ;
1117import { useDialogs } from "@/components/ui/dialog-provider" ;
18+ import {
19+ Collapsible ,
20+ CollapsibleTrigger ,
21+ CollapsibleContent ,
22+ } from "@/components/ui/collapsible" ;
23+
24+ type DocumentItem = { id : number ; name : string | null ; lastModified : Date | null } ;
25+
26+ function DocumentCard ( {
27+ id,
28+ name,
29+ lastModified,
30+ actions,
31+ } : {
32+ id : number ;
33+ name : string | null ;
34+ lastModified : Date | null ;
35+ actions ?: ReactNode ;
36+ } ) {
37+ const displayName = getDocumentName ( { id, name } ) ;
38+
39+ return (
40+ < ResourceCard
41+ preview = { < FileText className = "h-10 w-10 text-muted-foreground" /> }
42+ name = { displayName }
43+ date = { lastModified ? formatRelativeTime ( lastModified ) : "No versions" }
44+ href = { `/editor/${ id } ` }
45+ actions = { actions }
46+ />
47+ ) ;
48+ }
49+
50+ function useDocumentActions ( ) {
51+ const router = useRouter ( ) ;
52+ const [ isPending , startTransition ] = useTransition ( ) ;
53+ const { prompt, alert, confirm } = useDialogs ( ) ;
54+
55+ async function handleRename ( id : number , currentName : string ) {
56+ const newName = await prompt ( { title : `Rename "${ currentName } "` , label : "New name" , defaultValue : currentName } ) ;
57+ if ( newName === null || newName . trim ( ) === "" || newName . trim ( ) === currentName ) return ;
58+ startTransition ( async ( ) => {
59+ const result = await runAction ( renameDocumentAction ( { id, name : newName . trim ( ) } ) ) ;
60+ if ( result . success ) {
61+ router . refresh ( ) ;
62+ } else {
63+ await alert ( result . error ) ;
64+ }
65+ } ) ;
66+ }
67+
68+ async function handleArchive ( id : number , displayName : string ) {
69+ const confirmed = await confirm ( { message : `Archive "${ displayName } "?` , actionLabel : "Archive" } ) ;
70+ if ( ! confirmed ) return ;
71+ startTransition ( async ( ) => {
72+ const result = await runAction ( archiveDocumentAction ( { id } ) ) ;
73+ if ( result . success ) {
74+ router . refresh ( ) ;
75+ } else {
76+ await alert ( result . error ) ;
77+ }
78+ } ) ;
79+ }
80+
81+ async function handleUnarchive ( id : number , displayName : string ) {
82+ const confirmed = await confirm ( { message : `Unarchive "${ displayName } "?` , actionLabel : "Unarchive" } ) ;
83+ if ( ! confirmed ) return ;
84+ startTransition ( async ( ) => {
85+ const result = await runAction ( unarchiveDocumentAction ( { id } ) ) ;
86+ if ( result . success ) {
87+ router . refresh ( ) ;
88+ } else {
89+ await alert ( result . error ) ;
90+ }
91+ } ) ;
92+ }
93+
94+ return { isPending, handleRename, handleArchive, handleUnarchive } ;
95+ }
1296
1397function NewDocumentCard ( ) {
1498 const router = useRouter ( ) ;
1599 const [ isCreating , startTransition ] = useTransition ( ) ;
16100 const { prompt, alert } = useDialogs ( ) ;
17101
18102 async function handleCreateDocument ( ) {
19- const name = await prompt ( { title : "Document name " } ) ;
103+ const name = await prompt ( { title : "Create document" , label : "Name " } ) ;
20104
21105 if ( name === null ) {
22106 return ;
@@ -52,63 +136,8 @@ function NewDocumentCard() {
52136 ) ;
53137}
54138
55- function DocumentCard ( {
56- id,
57- name,
58- lastModified,
59- onRename,
60- disabled,
61- } : {
62- id : number ;
63- name : string | null ;
64- lastModified : Date | null ;
65- onRename : ( id : number , currentName : string ) => void ;
66- disabled : boolean ;
67- } ) {
68- const displayName = getDocumentName ( { id, name } ) ;
69-
70- return (
71- < Link href = { `/editor/${ id } ` } className = "block" >
72- < ResourceCard
73- preview = { < FileText className = "h-10 w-10 text-gray-400" /> }
74- name = { displayName }
75- date = { lastModified ? formatRelativeTime ( lastModified ) : "No versions" }
76- actions = {
77- < ActionButton
78- onClick = { ( e ) => {
79- e . preventDefault ( ) ;
80- onRename ( id , displayName ) ;
81- } }
82- disabled = { disabled }
83- title = "Rename"
84- >
85- < Pencil className = "h-3 w-3" />
86- </ ActionButton >
87- }
88- />
89- </ Link >
90- ) ;
91- }
92-
93- export function DocumentList ( { documents } : {
94- documents : { id : number ; name : string | null ; lastModified : Date | null } [ ] ;
95- } ) {
96- const router = useRouter ( ) ;
97- const [ isPending , startTransition ] = useTransition ( ) ;
98- const { prompt, alert } = useDialogs ( ) ;
99-
100- async function handleRename ( id : number , currentName : string ) {
101- const newName = await prompt ( { title : "Rename document" , defaultValue : currentName } ) ;
102- if ( newName === null || newName . trim ( ) === "" || newName . trim ( ) === currentName ) return ;
103- startTransition ( async ( ) => {
104- const result = await runAction ( renameDocumentAction ( { id, name : newName . trim ( ) } ) ) ;
105- if ( result . success ) {
106- router . refresh ( ) ;
107- } else {
108- await alert ( result . error ) ;
109- }
110- } ) ;
111- }
139+ export function DocumentList ( { documents } : { documents : DocumentItem [ ] } ) {
140+ const { isPending, handleRename, handleArchive } = useDocumentActions ( ) ;
112141
113142 return (
114143 < div className = "flex flex-col gap-4" >
@@ -117,17 +146,101 @@ export function DocumentList({ documents }: {
117146 < div className = "flex flex-wrap gap-2" >
118147 < NewDocumentCard />
119148
120- { documents . map ( ( doc ) => (
121- < DocumentCard
122- key = { doc . id }
123- id = { doc . id }
124- name = { doc . name }
125- lastModified = { doc . lastModified }
126- onRename = { handleRename }
127- disabled = { isPending }
128- />
129- ) ) }
149+ { documents . map ( ( doc ) => {
150+ const displayName = getDocumentName ( doc ) ;
151+ return (
152+ < DocumentCard
153+ key = { doc . id }
154+ id = { doc . id }
155+ name = { doc . name }
156+ lastModified = { doc . lastModified }
157+ actions = {
158+ < >
159+ < Button
160+ variant = "ghost"
161+ size = "icon-xs"
162+ onClick = { ( e ) => {
163+ e . preventDefault ( ) ;
164+ e . stopPropagation ( ) ;
165+ handleRename ( doc . id , displayName ) ;
166+ } }
167+ disabled = { isPending }
168+ title = "Rename"
169+ >
170+ < Pencil className = "h-3 w-3" />
171+ </ Button >
172+ < Button
173+ variant = "ghost"
174+ size = "icon-xs"
175+ onClick = { ( e ) => {
176+ e . preventDefault ( ) ;
177+ e . stopPropagation ( ) ;
178+ handleArchive ( doc . id , displayName ) ;
179+ } }
180+ disabled = { isPending }
181+ title = "Archive"
182+ >
183+ < Archive className = "h-3 w-3" />
184+ </ Button >
185+ </ >
186+ }
187+ />
188+ ) ;
189+ } ) }
130190 </ div >
131191 </ div >
132192 ) ;
133193}
194+
195+ export function ArchivedDocumentList ( { documents } : { documents : DocumentItem [ ] } ) {
196+ const [ open , setOpen ] = useState ( false ) ;
197+ const { isPending, handleUnarchive } = useDocumentActions ( ) ;
198+
199+ if ( documents . length === 0 ) return null ;
200+
201+ return (
202+ < Collapsible open = { open } onOpenChange = { setOpen } >
203+ < div className = "flex items-center gap-2" >
204+ < h2 className = "text-2xl font-bold leading-none" > Archived</ h2 >
205+ < CollapsibleTrigger
206+ render = { < Button variant = "ghost" size = "icon-xs" /> }
207+ >
208+ < ChevronDown
209+ className = { `h-4 w-4 transition-transform ${ open ? "rotate-180" : "" } ` }
210+ />
211+ </ CollapsibleTrigger >
212+ </ div >
213+
214+ < CollapsibleContent >
215+ < div className = "mt-4 flex flex-wrap gap-2" >
216+ { documents . map ( ( doc ) => {
217+ const displayName = getDocumentName ( doc ) ;
218+ return (
219+ < DocumentCard
220+ key = { doc . id }
221+ id = { doc . id }
222+ name = { doc . name }
223+ lastModified = { doc . lastModified }
224+ actions = {
225+ < Button
226+ variant = "ghost"
227+ size = "icon-xs"
228+ onClick = { ( e ) => {
229+ e . preventDefault ( ) ;
230+ e . stopPropagation ( ) ;
231+ handleUnarchive ( doc . id , displayName ) ;
232+ } }
233+ disabled = { isPending }
234+ title = "Unarchive"
235+ >
236+ < ArchiveRestore className = "h-3 w-3" />
237+ </ Button >
238+ }
239+ />
240+ ) ;
241+ } ) }
242+ </ div >
243+ </ CollapsibleContent >
244+ </ Collapsible >
245+ ) ;
246+ }
0 commit comments