All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Fixes all 542 ESLint problems in the web package to achieve 0 errors and 0 warnings. Changes: - Fixed 144 issues: nullish coalescing, return types, unused variables - Fixed 118 issues: unnecessary conditions, type safety, template literals - Fixed 79 issues: non-null assertions, unsafe assignments, empty functions - Fixed 67 issues: explicit return types, promise handling, enum comparisons - Fixed 45 final warnings: missing return types, optional chains - Fixed 25 typecheck-related issues: async/await, type assertions, formatting - Fixed JSX.Element namespace errors across 90+ files All Quality Rails violations resolved. Lint and typecheck both pass with 0 problems. Files modified: 118 components, tests, hooks, and utilities Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
148 lines
4.8 KiB
TypeScript
148 lines
4.8 KiB
TypeScript
"use client";
|
|
|
|
import React from "react";
|
|
import type { KnowledgeTag } from "@mosaic/shared";
|
|
import { EntryStatus, Visibility } from "@mosaic/shared";
|
|
|
|
interface EntryMetadataProps {
|
|
title: string;
|
|
status: EntryStatus;
|
|
visibility: Visibility;
|
|
selectedTags: string[];
|
|
availableTags: KnowledgeTag[];
|
|
onTitleChange: (title: string) => void;
|
|
onStatusChange: (status: EntryStatus) => void;
|
|
onVisibilityChange: (visibility: Visibility) => void;
|
|
onTagsChange: (tags: string[]) => void;
|
|
}
|
|
|
|
/**
|
|
* EntryMetadata - Title, tags, status, and visibility controls
|
|
*/
|
|
export function EntryMetadata({
|
|
title,
|
|
status,
|
|
visibility,
|
|
selectedTags,
|
|
availableTags,
|
|
onTitleChange,
|
|
onStatusChange,
|
|
onVisibilityChange,
|
|
onTagsChange,
|
|
}: EntryMetadataProps): React.JSX.Element {
|
|
const handleTagToggle = (tagId: string): void => {
|
|
if (selectedTags.includes(tagId)) {
|
|
onTagsChange(selectedTags.filter((id) => id !== tagId));
|
|
} else {
|
|
onTagsChange([...selectedTags, tagId]);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="entry-metadata space-y-4">
|
|
{/* Title */}
|
|
<div>
|
|
<label
|
|
htmlFor="entry-title"
|
|
className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
|
|
>
|
|
Title
|
|
</label>
|
|
<input
|
|
id="entry-title"
|
|
type="text"
|
|
value={title}
|
|
onChange={(e) => {
|
|
onTitleChange(e.target.value);
|
|
}}
|
|
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
placeholder="Entry title..."
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
{/* Status and Visibility Row */}
|
|
<div className="grid grid-cols-2 gap-4">
|
|
{/* Status */}
|
|
<div>
|
|
<label
|
|
htmlFor="entry-status"
|
|
className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
|
|
>
|
|
Status
|
|
</label>
|
|
<select
|
|
id="entry-status"
|
|
value={status}
|
|
onChange={(e) => {
|
|
onStatusChange(e.target.value as EntryStatus);
|
|
}}
|
|
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
>
|
|
<option value={EntryStatus.DRAFT}>Draft</option>
|
|
<option value={EntryStatus.PUBLISHED}>Published</option>
|
|
<option value={EntryStatus.ARCHIVED}>Archived</option>
|
|
</select>
|
|
</div>
|
|
|
|
{/* Visibility */}
|
|
<div>
|
|
<label
|
|
htmlFor="entry-visibility"
|
|
className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
|
|
>
|
|
Visibility
|
|
</label>
|
|
<select
|
|
id="entry-visibility"
|
|
value={visibility}
|
|
onChange={(e) => {
|
|
onVisibilityChange(e.target.value as Visibility);
|
|
}}
|
|
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
>
|
|
<option value={Visibility.PRIVATE}>Private</option>
|
|
<option value={Visibility.WORKSPACE}>Workspace</option>
|
|
<option value={Visibility.PUBLIC}>Public</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Tags */}
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
Tags
|
|
</label>
|
|
<div className="flex flex-wrap gap-2">
|
|
{availableTags.length > 0 ? (
|
|
availableTags.map((tag) => {
|
|
const isSelected = selectedTags.includes(tag.id);
|
|
return (
|
|
<button
|
|
key={tag.id}
|
|
type="button"
|
|
onClick={() => {
|
|
handleTagToggle(tag.id);
|
|
}}
|
|
className={`px-3 py-1 rounded-full text-sm font-medium transition-colors ${
|
|
isSelected
|
|
? "bg-blue-600 text-white"
|
|
: "bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-300 dark:hover:bg-gray-600"
|
|
}`}
|
|
style={isSelected && tag.color ? { backgroundColor: tag.color } : undefined}
|
|
>
|
|
{tag.name}
|
|
</button>
|
|
);
|
|
})
|
|
) : (
|
|
<p className="text-sm text-gray-500 dark:text-gray-400">
|
|
No tags available. Create tags first.
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|