improvement: all demo bugs fixed. give me more baby.

This commit is contained in:
2026-07-08 02:53:47 +03:30
parent 86b1e3afff
commit 8d334833ff
23 changed files with 876 additions and 626 deletions

View File

@@ -1,10 +1,9 @@
'use client';
import { useCallback, useEffect, useState } from 'react';
import { useCallback, useEffect, useState, type KeyboardEvent } from 'react';
import { useTranslations } from 'next-intl';
import { Eye, EyeOff } from 'lucide-react';
import { Eye, EyeOff, Send } from 'lucide-react';
import { formatApiErrorMessage } from '@/components/shared/formatApiError';
import { Button } from '@/components/ui/shared/Button';
import type { LabCaseComment } from '@/types/cases';
interface LabCaseCommentsPanelProps {
@@ -15,6 +14,13 @@ interface LabCaseCommentsPanelProps {
onPost: (body: string, visibleToClinic?: boolean) => Promise<LabCaseComment>;
onToggleVisibility?: (commentId: string, visible: boolean) => Promise<LabCaseComment>;
onError?: (message: string) => void;
/**
* Deferred composer: the parent owns the draft value and triggers the post
* elsewhere (e.g. the "Send to lab" button). No send icon is shown.
*/
deferSubmit?: boolean;
composerValue?: string;
onComposerValueChange?: (value: string) => void;
}
export function LabCaseCommentsPanel({
@@ -25,6 +31,9 @@ export function LabCaseCommentsPanel({
onPost,
onToggleVisibility,
onError,
deferSubmit = false,
composerValue,
onComposerValueChange,
}: LabCaseCommentsPanelProps) {
const t = useTranslations('caseComments');
const [comments, setComments] = useState<LabCaseComment[]>([]);
@@ -51,7 +60,7 @@ export function LabCaseCommentsPanel({
async function handlePost() {
const trimmed = body.trim();
if (!trimmed || !canPost) return;
if (!trimmed || !canPost || posting) return;
setPosting(true);
try {
const created = await onPost(trimmed, visibleToClinic);
@@ -65,6 +74,13 @@ export function LabCaseCommentsPanel({
}
}
function handleComposerKeyDown(event: KeyboardEvent<HTMLTextAreaElement>) {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
void handlePost();
}
}
async function handleToggle(comment: LabCaseComment) {
if (!onToggleVisibility || !canToggleVisibility) return;
try {
@@ -112,12 +128,8 @@ export function LabCaseCommentsPanel({
type="button"
onClick={() => void handleToggle(comment)}
className="shrink-0 p-1 rounded hover:bg-border text-text-muted"
title={
comment.visibleToClinic ? t('makeHidden') : t('makeVisible')
}
aria-label={
comment.visibleToClinic ? t('makeHidden') : t('makeVisible')
}
title={comment.visibleToClinic ? t('makeHidden') : t('makeVisible')}
aria-label={comment.visibleToClinic ? t('makeHidden') : t('makeVisible')}
>
{comment.visibleToClinic ? (
<Eye className="h-4 w-4" />
@@ -132,33 +144,54 @@ export function LabCaseCommentsPanel({
</ul>
)}
{canPost ? (
<div className="space-y-2 border-t border-border pt-2">
{canPost && deferSubmit ? (
<div className="border-t border-border pt-2">
<textarea
value={body}
onChange={(e) => setBody(e.target.value)}
value={composerValue ?? ''}
onChange={(e) => onComposerValueChange?.(e.target.value)}
placeholder={t('placeholder')}
rows={2}
className="w-full rounded-md border border-border bg-surface px-3 py-2 text-sm resize-none"
/>
{canToggleVisibility ? (
<label className="flex items-center gap-2 text-xs text-text-muted cursor-pointer">
<input
type="checkbox"
checked={visibleToClinic}
onChange={(e) => setVisibleToClinic(e.target.checked)}
/>
{t('visibleToClinicToggle')}
</label>
) : null}
<Button
type="button"
size="sm"
disabled={posting || !body.trim()}
onClick={() => void handlePost()}
>
{t('post')}
</Button>
</div>
) : canPost ? (
<div className="flex items-end gap-2 border-t border-border pt-2">
<textarea
value={body}
onChange={(e) => setBody(e.target.value)}
onKeyDown={handleComposerKeyDown}
placeholder={t('placeholder')}
rows={2}
className="min-w-0 flex-1 rounded-md border border-border bg-surface px-3 py-2 text-sm resize-none"
/>
<div className="flex items-center gap-1 pb-1">
{canToggleVisibility ? (
<button
type="button"
onClick={() => setVisibleToClinic((v) => !v)}
className={`shrink-0 p-2 rounded-md border transition-colors ${
visibleToClinic
? 'border-primary/40 bg-primary/10 text-primary'
: 'border-border text-text-muted hover:text-text-primary'
}`}
title={visibleToClinic ? t('composerVisible') : t('composerHidden')}
aria-label={visibleToClinic ? t('composerVisible') : t('composerHidden')}
aria-pressed={visibleToClinic}
>
{visibleToClinic ? <Eye className="h-4 w-4" /> : <EyeOff className="h-4 w-4" />}
</button>
) : null}
<button
type="button"
onClick={() => void handlePost()}
disabled={posting || !body.trim()}
className="shrink-0 p-2 rounded-md bg-primary text-white transition-colors hover:opacity-90 disabled:opacity-40 disabled:cursor-not-allowed"
title={t('send')}
aria-label={t('send')}
>
<Send className="h-4 w-4" />
</button>
</div>
</div>
) : null}
</div>