@@ -64,6 +64,7 @@ const checklistItems = ref<ChecklistItem[]>([]);
6464const checklistSummary = ref (' ' );
6565const tagsInput = ref (' ' );
6666const loading = ref (false );
67+ const submitted = ref (false );
6768const checklistListRef = ref <HTMLElement | null >(null );
6869const titleInputRef = ref <HTMLInputElement | null >(null );
6970let checklistSortable: Sortable | null = null ;
@@ -105,19 +106,24 @@ watch(
105106 title .value = card .title ?? ' ' ;
106107 cardType .value = card .type as ' NOTE' | ' LINK' | ' CHECKLIST' ;
107108 try {
108- const pl = typeof card .payload === ' string' ? JSON .parse (card .payload || ' {}' ) : card .payload ;
109+ const pl =
110+ typeof card .payload === ' string' ? JSON .parse (card .payload || ' {}' ) : card .payload ;
109111 if (card .type === ' NOTE' ) {
110112 noteContent .value = (pl as { content? : string }).content ?? ' ' ;
111113 noteSummary .value = (pl as { summary? : string }).summary ?? ' ' ;
112114 } else if (card .type === ' LINK' ) {
113115 linkUrl .value = (pl as { url? : string }).url ?? ' ' ;
114116 linkSummary .value = (pl as { summary? : string }).summary ?? ' ' ;
115117 } else if (card .type === ' CHECKLIST' ) {
116- const items = (pl as { items? : { id: string ; text: string ; done: boolean ; order: number }[] }).items ?? [];
117- checklistItems .value = items .length ? items .map ((i ) => ({ ... i })) : [{ id: crypto .randomUUID (), text: ' ' , done: false , order: 100 }];
118+ const items =
119+ (pl as { items? : { id: string ; text: string ; done: boolean ; order: number }[] })
120+ .items ?? [];
121+ checklistItems .value = items .length
122+ ? items .map ((i ) => ({ ... i }))
123+ : [{ id: crypto .randomUUID (), text: ' ' , done: false , order: 100 }];
118124 checklistSummary .value = (pl as { summary? : string }).summary ?? ' ' ;
119125 }
120- tagsInput .value = Array .isArray (card .tags ) ? card .tags .join (' , ' ) : ' ' ;
126+ tagsInput .value = Array .isArray (card .tags ) ? card .tags .join (' , ' ) : ' ' ;
121127 } catch {
122128 noteContent .value = ' ' ;
123129 noteSummary .value = ' ' ;
@@ -141,6 +147,7 @@ watch(
141147 tagsInput .value = ' ' ;
142148 if (! props .columnId && props .workspaceId == null ) workspaceStore .fetchWorkspaces ();
143149 }
150+ submitted .value = false ;
144151 nextTick (() => {
145152 initChecklistSortable ();
146153 titleInputRef .value ?.focus ();
@@ -165,7 +172,9 @@ onUnmounted(() => {
165172});
166173
167174function addChecklistItem() {
168- const maxOrder = checklistItems .value .length ? Math .max (... checklistItems .value .map ((i ) => i .order )) : 0 ;
175+ const maxOrder = checklistItems .value .length
176+ ? Math .max (... checklistItems .value .map ((i ) => i .order ))
177+ : 0 ;
169178 checklistItems .value .push ({
170179 id: crypto .randomUUID (),
171180 text: ' ' ,
@@ -219,6 +228,8 @@ function buildPayload(): string {
219228}
220229
221230async function handleSubmit() {
231+ submitted .value = true ;
232+
222233 if (cardType .value === ' LINK' && ! linkUrl .value .trim ()) {
223234 toast .error (' Введите URL ссылки' );
224235 return ;
@@ -282,7 +293,10 @@ async function handleSubmit() {
282293 <DialogRoot v-model:open =" openProxy" >
283294 <DialogPortal >
284295 <DialogOverlay class =" dialog-overlay" @click =" openProxy = false" />
285- <DialogContent class =" dialog-content max-h-[90vh] overflow-y-auto" @escape-key-down =" openProxy = false" >
296+ <DialogContent
297+ class =" dialog-content max-h-[90vh] overflow-y-auto"
298+ @escape-key-down =" openProxy = false"
299+ >
286300 <div class =" dialog-header" >
287301 <DialogTitle class =" dialog-title" >
288302 {{ isEditMode ? 'Редактировать карточку' : 'Новая карточка' }}
@@ -298,39 +312,54 @@ async function handleSubmit() {
298312 <div class =" flex gap-2 mb-3" >
299313 <button
300314 type =" button"
301- class =" flex-1 h-9 px-3 text-sm rounded-[var(--r)] flex-center gap-1.5 transition-colors"
302- :class =" addDestination === 'hub' ? 'bg-primary text-on-primary' : 'bg-surface border border-border text-muted hover:text-fg'"
303- @click =" addDestination = 'hub'; selectedWorkspaceId = ''"
315+ class =" flex-1 h-9 px-3 border border-border text-sm rounded-[var(--r)] flex-center gap-1.5 transition-colors"
316+ :class ="
317+ addDestination === 'hub'
318+ ? 'bg-primary text-on-primary'
319+ : 'bg-surface text-muted hover:text-fg'
320+ "
321+ @click ="
322+ addDestination = 'hub';
323+ selectedWorkspaceId = '';
324+ "
304325 >
305326 <span class =" i-lucide-inbox" />
306327 Хаб
307328 </button >
308329 <button
309330 type =" button"
310- class =" flex-1 h-9 px-3 text-sm rounded-[var(--r)] flex-center gap-1.5 transition-colors"
311- :class =" addDestination === 'workspace' ? 'bg-primary text-on-primary' : 'bg-surface border border-border text-muted hover:text-fg'"
331+ class =" flex-1 h-9 px-3 border border-border text-sm rounded-[var(--r)] flex-center gap-1.5 transition-colors"
332+ :class ="
333+ addDestination === 'workspace'
334+ ? 'bg-primary text-on-primary'
335+ : 'bg-surface text-muted hover:text-fg'
336+ "
312337 @click =" addDestination = 'workspace'"
313338 >
314339 <span class =" i-lucide-layout-grid" />
315340 Воркспейс
316341 </button >
317342 </div >
318- <div v-if =" addDestination === 'workspace'" class =" mt-2" >
319- <label for =" card-workspace" class =" block text-sm text-muted mb-1" >Воркспейс (карточка попадёт в беклог)</label >
320- <select
321- id =" card-workspace"
322- v-model =" selectedWorkspaceId"
323- class =" input py-2 max-w-full truncate"
324- >
325- <option value =" " disabled hidden >Выберите воркспейс</option >
326- <option
327- v-for =" w in workspaces"
328- :key =" w.id"
329- :value =" w.id"
343+ <div class =" mt-2 h-[72px]" >
344+ <template v-if =" addDestination === ' hub' " >
345+ <p class =" text-sm text-muted text-center h-full flex-center" >Карточка попадёт в общий список без привязки к воркспейсу</p >
346+ </template >
347+ <template v-else >
348+ <label for =" card-workspace" class =" block text-sm text-muted mb-1"
349+ >Воркспейс * (карточка попадёт в беклог)</label
350+ >
351+ <select
352+ id =" card-workspace"
353+ v-model =" selectedWorkspaceId"
354+ class =" input py-2 max-w-full truncate"
355+ :class =" submitted && !selectedWorkspaceId && 'border-danger!'"
330356 >
331- {{ w.title.length > 40 ? w.title.slice(0, 40) + '…' : w.title }}
332- </option >
333- </select >
357+ <option value =" " disabled hidden >Выберите воркспейс</option >
358+ <option v-for =" w in workspaces" :key =" w.id" :value =" w.id" >
359+ {{ w.title.length > 40 ? w.title.slice(0, 40) + '…' : w.title }}
360+ </option >
361+ </select >
362+ </template >
334363 </div >
335364 </div >
336365
@@ -346,17 +375,6 @@ async function handleSubmit() {
346375 />
347376 </div >
348377
349- <div >
350- <label for =" card-tags" class =" block text-sm font-medium mb-1" >Теги</label >
351- <input
352- id =" card-tags"
353- v-model =" tagsInput"
354- type =" text"
355- class =" input"
356- placeholder =" Через запятую, например: работа, идеи"
357- />
358- </div >
359-
360378 <div v-if =" !isEditMode" >
361379 <label class =" block text-sm font-medium mb-2" >Тип</label >
362380 <div class =" flex gap-2" >
@@ -365,8 +383,12 @@ async function handleSubmit() {
365383 :key =" t.value"
366384 type =" button"
367385 @click =" cardType = t.value"
368- class =" flex-1 h-9 px-3 text-sm rounded-[var(--r)] flex-center gap-1.5 transition-colors"
369- :class =" cardType === t.value ? 'bg-primary text-on-primary' : 'bg-surface border border-border text-muted hover:text-fg'"
386+ class =" flex-1 h-9 px-3 border border-border text-sm rounded-[var(--r)] flex-center gap-1.5 transition-colors"
387+ :class ="
388+ cardType === t.value
389+ ? 'bg-primary text-on-primary'
390+ : 'bg-surface text-muted hover:text-fg'
391+ "
370392 >
371393 <span :class =" t.icon" />
372394 {{ t.label }}
@@ -375,7 +397,7 @@ async function handleSubmit() {
375397 </div >
376398
377399 <template v-if =" cardType === ' NOTE' " >
378- <div >
400+ <div class = " min-h-[152px] " >
379401 <label for =" card-note" class =" block text-sm font-medium mb-1" >Текст заметки</label >
380402 <textarea
381403 id =" card-note"
@@ -385,7 +407,17 @@ async function handleSubmit() {
385407 />
386408 </div >
387409 <div >
388- <label for =" card-summary-note" class =" block text-sm font-medium mb-1" >Конспект</label >
410+ <label for =" card-tags-note" class =" block text-sm font-medium mb-1" >Теги</label >
411+ <input
412+ id =" card-tags-note"
413+ v-model =" tagsInput"
414+ type =" text"
415+ class =" input"
416+ placeholder =" Через запятую: работа, идеи"
417+ />
418+ </div >
419+ <div >
420+ <label for =" card-summary-note" class =" block text-sm font-medium mb-1" >Заметка</label >
389421 <input
390422 id =" card-summary-note"
391423 v-model =" noteSummary"
@@ -397,18 +429,29 @@ async function handleSubmit() {
397429 </template >
398430
399431 <template v-if =" cardType === ' LINK' " >
400- <div >
432+ <div class = " min-h-[152px] " >
401433 <label for =" card-url" class =" block text-sm font-medium mb-1" >URL *</label >
402434 <input
403435 id =" card-url"
404436 v-model =" linkUrl"
405437 type =" url"
406438 class =" input"
439+ :class =" submitted && !linkUrl.trim() && 'border-danger!'"
407440 placeholder =" https://..."
408441 />
409442 </div >
410443 <div >
411- <label for =" card-summary-link" class =" block text-sm font-medium mb-1" >Конспект</label >
444+ <label for =" card-tags-link" class =" block text-sm font-medium mb-1" >Теги</label >
445+ <input
446+ id =" card-tags-link"
447+ v-model =" tagsInput"
448+ type =" text"
449+ class =" input"
450+ placeholder =" Через запятую: работа, идеи"
451+ />
452+ </div >
453+ <div >
454+ <label for =" card-summary-link" class =" block text-sm font-medium mb-1" >Заметка</label >
412455 <input
413456 id =" card-summary-link"
414457 v-model =" linkSummary"
@@ -420,15 +463,17 @@ async function handleSubmit() {
420463 </template >
421464
422465 <template v-if =" cardType === ' CHECKLIST' " >
423- <div >
466+ <div class = " min-h-[152px] " >
424467 <label class =" block text-sm font-medium mb-2" >Пункты</label >
425468 <div ref =" checklistListRef" class =" space-y-2" >
426469 <div
427470 v-for =" (item, index) in checklistItems"
428471 :key =" item.id"
429472 class =" flex items-center gap-2"
430473 >
431- <span class =" checklist-grip i-lucide-grip-vertical text-muted cursor-grab active:cursor-grabbing" />
474+ <span
475+ class =" checklist-grip i-lucide-grip-vertical text-muted cursor-grab active:cursor-grabbing"
476+ />
432477 <input
433478 v-model =" item.text"
434479 type =" text"
@@ -455,7 +500,19 @@ async function handleSubmit() {
455500 </button >
456501 </div >
457502 <div >
458- <label for =" card-summary-checklist" class =" block text-sm font-medium mb-1" >Конспект</label >
503+ <label for =" card-tags-checklist" class =" block text-sm font-medium mb-1" >Теги</label >
504+ <input
505+ id =" card-tags-checklist"
506+ v-model =" tagsInput"
507+ type =" text"
508+ class =" input"
509+ placeholder =" Через запятую: работа, идеи"
510+ />
511+ </div >
512+ <div >
513+ <label for =" card-summary-checklist" class =" block text-sm font-medium mb-1"
514+ >Заметка</label
515+ >
459516 <input
460517 id =" card-summary-checklist"
461518 v-model =" checklistSummary"
@@ -467,12 +524,7 @@ async function handleSubmit() {
467524 </template >
468525
469526 <div class =" flex justify-between items-center gap-3 pt-4" >
470- <button
471- v-if =" isEditMode"
472- type =" button"
473- class =" btn-delete"
474- @click =" emit('delete')"
475- >
527+ <button v-if =" isEditMode" type =" button" class =" btn-delete" @click =" emit('delete')" >
476528 <span class =" i-lucide-trash-2" />
477529 Удалить
478530 </button >
0 commit comments