feat: add CustomButton component and implement conditional UI rendering based on user permissions and document status

This commit is contained in:
Yashwin 2026-05-12 19:00:16 +05:30
parent 0af7da47a9
commit 40e43389df
7 changed files with 412 additions and 176 deletions

View File

@ -0,0 +1,119 @@
import type { ReactElement, ButtonHTMLAttributes } from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
import { useAppTheme } from '@/hooks/useAppTheme';
import { Loader2 } from 'lucide-react';
const customButtonVariants = cva(
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-xs font-medium transition-all disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer outline-none active:scale-[0.98]',
{
variants: {
variant: {
primary: '', // Handled by style prop for theme colors
secondary: '', // Handled by style prop for theme colors
success: 'bg-emerald-600 text-white hover:bg-emerald-700',
danger: 'bg-red-600 text-white hover:bg-red-700',
warning: 'bg-amber-500 text-white hover:bg-amber-600',
outline: 'border border-gray-200 bg-white text-gray-700 hover:bg-gray-50',
ghost: 'bg-transparent text-gray-600 hover:bg-gray-100',
link: 'bg-transparent text-primary underline-offset-4 hover:underline p-0 h-auto',
},
size: {
xs: 'h-7 px-2 text-[10px]',
sm: 'h-8 px-3',
md: 'h-9 px-4',
lg: 'h-10 px-6 text-sm',
xl: 'h-12 px-8 text-base',
icon: 'h-9 w-9 p-0',
},
fullWidth: {
true: 'w-full',
}
},
defaultVariants: {
variant: 'primary',
size: 'md',
},
}
);
interface CustomButtonProps
extends ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof customButtonVariants> {
isLoading?: boolean;
leftIcon?: React.ReactNode;
rightIcon?: React.ReactNode;
}
export const CustomButton = ({
children,
variant,
size,
fullWidth,
className,
disabled,
isLoading,
leftIcon,
rightIcon,
style,
...props
}: CustomButtonProps): ReactElement => {
const { primaryColor, secondaryColor } = useAppTheme();
const getThemeStyles = () => {
if (variant === 'secondary') {
return {
backgroundColor: secondaryColor,
color: primaryColor,
border: `1px solid ${primaryColor}20`,
};
}
if (variant === 'primary' || !variant) {
return {
backgroundColor: primaryColor,
color: secondaryColor,
};
}
return {};
};
return (
<button
disabled={disabled || isLoading}
className={cn(customButtonVariants({ variant, size, fullWidth, className }))}
style={{
...getThemeStyles(),
...style,
}}
onMouseEnter={(e) => {
if (!disabled && !isLoading) {
if (variant === 'primary' || !variant) {
e.currentTarget.style.filter = 'brightness(1.1)';
} else if (variant === 'secondary') {
e.currentTarget.style.backgroundColor = `${primaryColor}10`;
}
}
}}
onMouseLeave={(e) => {
if (!disabled && !isLoading) {
const styles = getThemeStyles() as any;
if (styles.backgroundColor) e.currentTarget.style.backgroundColor = styles.backgroundColor;
if (styles.color) e.currentTarget.style.color = styles.color;
e.currentTarget.style.filter = 'none';
}
}}
{...props}
>
{isLoading ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<>
{leftIcon && <span className="shrink-0">{leftIcon}</span>}
{children}
{rightIcon && <span className="shrink-0">{rightIcon}</span>}
</>
)}
</button>
);
};

View File

@ -11,7 +11,8 @@ import {
ExternalLink,
} from "lucide-react";
import { cn } from "@/lib/utils";
import { Modal } from "./Modal";
import { Modal, CustomButton } from "./";
import { useAppTheme } from "@/hooks/useAppTheme";
import fileAttachmentService, { type FileAttachment } from "@/services/file-attachment-service";
import { DeleteConfirmationModal } from "./DeleteConfirmationModal";
@ -28,7 +29,8 @@ export const FileShareModal: React.FC<FileShareModalProps> = ({
}) => {
const [expiresInHours, setExpiresInHours] = useState<number>(24);
const [maxDownloads, setMaxDownloads] = useState<number | "">("");
const [permissions, setPermissions] = useState<"view" | "download">("download");
const permissions = "download";
const { primaryColor } = useAppTheme();
const [isSharing, setIsSharing] = useState(false);
const [shareData, setShareData] = useState<{ url: string; token: string; id: string } | null>(null);
@ -124,13 +126,18 @@ export const FileShareModal: React.FC<FileShareModalProps> = ({
{[1, 24, 72, 168].map((h) => (
<button
key={h}
type="button"
onClick={() => setExpiresInHours(h)}
className={cn(
"py-2 text-xs font-medium rounded-lg border transition-all",
"py-2 text-xs font-medium rounded-lg border transition-all cursor-pointer",
expiresInHours === h
? "bg-[#084cc8] border-[#084cc8] text-white shadow-sm"
: "border-[rgba(0,0,0,0.08)] text-[#475569] hover:border-[#084cc8]/30 hover:bg-gray-50"
? "text-white shadow-sm"
: "border-[rgba(0,0,0,0.08)] text-[#475569] hover:bg-gray-50"
)}
style={expiresInHours === h ? {
backgroundColor: primaryColor,
borderColor: primaryColor
} : {}}
>
{h === 1 ? "1 Hr" : h === 24 ? "1 Day" : h === 72 ? "3 Days" : "7 Days"}
</button>
@ -150,7 +157,11 @@ export const FileShareModal: React.FC<FileShareModalProps> = ({
value={maxDownloads}
onChange={(e) => setMaxDownloads(e.target.value === "" ? "" : Number(e.target.value))}
placeholder="Unlimited"
className="w-full h-10 border border-[rgba(0,0,0,0.12)] rounded-lg px-3 text-sm focus:outline-none focus:ring-1 focus:ring-[#084cc8] focus:border-[#084cc8]"
className="w-full h-10 border border-[rgba(0,0,0,0.12)] rounded-lg px-3 text-sm focus:outline-none focus:ring-1 transition-all"
style={{
'--tw-ring-color': primaryColor,
borderColor: 'rgba(0,0,0,0.12)'
} as React.CSSProperties}
/>
</div>
@ -160,34 +171,25 @@ export const FileShareModal: React.FC<FileShareModalProps> = ({
<Shield className="w-3 h-3" />
Permission
</label>
<select
value={permissions}
onChange={(e) => setPermissions(e.target.value as any)}
className="w-full h-10 border border-[rgba(0,0,0,0.12)] rounded-lg px-3 text-sm focus:outline-none focus:ring-1 focus:ring-[#084cc8] focus:border-[#084cc8]"
>
<option value="view">View Only</option>
<option value="download">Download</option>
</select>
<div className="w-full h-10 border border-[rgba(0,0,0,0.08)] bg-gray-50/50 rounded-lg px-3 flex items-center gap-2 text-sm text-[#475569] font-medium">
<Download className="w-3.5 h-3.5 text-[#9aa6b2]" />
Download
</div>
</div>
</div>
<button
<CustomButton
variant="primary"
fullWidth
size="lg"
onClick={handleCreateShare}
disabled={isSharing}
className="w-full h-11 bg-[#084cc8] hover:bg-[#0640aa] text-white rounded-xl text-sm font-bold transition-all shadow-lg shadow-[#084cc8]/20 flex items-center justify-center gap-2"
isLoading={isSharing}
leftIcon={<Share2 className="w-4 h-4" />}
className="rounded-xl shadow-lg"
style={{ boxShadow: `${primaryColor}33 0px 8px 24px` }}
>
{isSharing ? (
<>
<Loader2 className="w-4 h-4 animate-spin" />
Creating Link...
</>
) : (
<>
<Share2 className="w-4 h-4" />
Generate Secure Link
</>
)}
</button>
Generate Secure Link
</CustomButton>
</>
) : (
<div className="space-y-4 animate-in fade-in slide-in-from-bottom-2">
@ -208,16 +210,20 @@ export const FileShareModal: React.FC<FileShareModalProps> = ({
<label className="text-[10px] font-bold text-[#9aa6b2] uppercase tracking-widest">Share URL</label>
<div className="flex items-center gap-2">
<div className="flex-1 h-11 bg-gray-50 border border-[rgba(0,0,0,0.08)] rounded-xl px-3 flex items-center overflow-hidden">
<code className="text-[11px] text-[#084cc8] truncate font-mono">
<code
className="text-[11px] truncate font-mono"
style={{ color: primaryColor }}
>
{shareData.url}
</code>
</div>
<button
onClick={copyToClipboard}
className={cn(
"w-11 h-11 rounded-xl flex items-center justify-center transition-all",
copied ? "bg-emerald-500 text-white shadow-emerald-500/30" : "bg-[#084cc8] text-white shadow-[#084cc8]/30 hover:scale-105"
"w-11 h-11 rounded-xl flex items-center justify-center transition-all cursor-pointer",
copied ? "bg-emerald-500 text-white shadow-emerald-500/30" : "text-white hover:scale-105"
)}
style={!copied ? { backgroundColor: primaryColor, boxShadow: `${primaryColor}4D 0px 4px 12px` } : {}}
>
{copied ? <Check className="w-5 h-5" /> : <Copy className="w-5 h-5" />}
</button>
@ -226,19 +232,21 @@ export const FileShareModal: React.FC<FileShareModalProps> = ({
<div className="pt-2 flex flex-col gap-2">
<div className="flex gap-2">
<button
<CustomButton
variant="outline"
className="flex-1 rounded-xl"
onClick={() => setShareData(null)}
className="flex-1 h-10 border border-[rgba(0,0,0,0.1)] hover:bg-gray-50 rounded-xl text-xs font-bold text-[#475569] transition-all"
>
Create Another
</button>
<button
</CustomButton>
<CustomButton
variant="outline"
className="px-4 rounded-xl"
leftIcon={<ExternalLink className="w-3.5 h-3.5" />}
onClick={() => window.open(shareData.url, '_blank')}
className="h-10 px-4 flex items-center gap-2 border border-[rgba(0,0,0,0.1)] hover:bg-gray-50 rounded-xl text-xs font-bold text-[#475569] transition-all"
>
<ExternalLink className="w-3.5 h-3.5" />
Test Link
</button>
</CustomButton>
</div>
<button

View File

@ -1,5 +1,6 @@
export { PrimaryButton } from './PrimaryButton';
export { SecondaryButton } from './SecondaryButton';
export { CustomButton } from './CustomButton';
export { ThemeButton } from './ThemeButton';
export { ActionDropdown } from './ActionDropdown';
export { FormField } from './FormField';

View File

@ -14,8 +14,8 @@ import type { Department } from "@/types/department";
interface TreeItemProps {
item: Department;
level?: number;
onAddSub: (item: Department) => void;
onEdit: (item: Department) => void;
onAddSub?: (item: Department) => void;
onEdit?: (item: Department) => void;
onDelete?: (item: Department) => void;
}
@ -135,28 +135,32 @@ const TreeItem = ({
</div>
<div className="ml-auto flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
<button
onClick={() => onAddSub(item)}
className={`p-1.5 rounded transition-colors ${
isPrimaryActive
? "hover:bg-white/10 text-white"
: "hover:bg-gray-100 text-[#64748B]"
}`}
title="Add Sub-department"
>
<Plus className="w-4 h-4" />
</button>
<button
onClick={() => onEdit(item)}
className={`p-1.5 rounded transition-colors ${
isPrimaryActive
? "hover:bg-white/10 text-white"
: "hover:bg-gray-100 text-[#64748B]"
}`}
title="Edit"
>
<Edit2 className="w-4 h-4" />
</button>
{onAddSub && (
<button
onClick={() => onAddSub(item)}
className={`p-1.5 rounded transition-colors ${
isPrimaryActive
? "hover:bg-white/10 text-white"
: "hover:bg-gray-100 text-[#64748B]"
}`}
title="Add Sub-department"
>
<Plus className="w-4 h-4" />
</button>
)}
{onEdit && (
<button
onClick={() => onEdit(item)}
className={`p-1.5 rounded transition-colors ${
isPrimaryActive
? "hover:bg-white/10 text-white"
: "hover:bg-gray-100 text-[#64748B]"
}`}
title="Edit"
>
<Edit2 className="w-4 h-4" />
</button>
)}
{onDelete && (
<button
onClick={() => onDelete(item)}
@ -204,8 +208,8 @@ interface DepartmentTreeViewProps {
data: Department[];
isLoading: boolean;
error: string | null;
onAddSub: (item: Department) => void;
onEdit: (item: Department) => void;
onAddSub?: (item: Department) => void;
onEdit?: (item: Department) => void;
onDelete?: (item: Department) => void;
}

View File

@ -61,7 +61,7 @@ export const DepartmentsTable = forwardRef<
ref,
): ReactElement => {
const { primaryColor } = useAppTheme();
const { canUpdate } = usePermissions();
const { canUpdate, canCreate } = usePermissions();
const reduxTenantId = useSelector(
(state: RootState) => state.auth.tenantId,
);
@ -384,14 +384,22 @@ export const DepartmentsTable = forwardRef<
data={treeData}
isLoading={isLoading}
error={error}
onAddSub={(item) => {
setSelectedDepartment(item);
setIsNewModalOpen(true);
}}
onEdit={(item) => {
setSelectedDepartment(item);
setIsEditModalOpen(true);
}}
onAddSub={
canCreate("departments")
? (item) => {
setSelectedDepartment(item);
setIsNewModalOpen(true);
}
: undefined
}
onEdit={
canUpdate("departments")
? (item) => {
setSelectedDepartment(item);
setIsEditModalOpen(true);
}
: undefined
}
/>
)}

View File

@ -13,7 +13,7 @@ import {
import { documentService } from "@/services/document-service";
import type { DocumentCategory } from "@/types/document";
import { showToast } from "@/utils/toast";
import { ArrowLeft, FileText } from "lucide-react";
import { FileText } from "lucide-react";
import { moduleService } from "@/services/module-service";
import type { MyModule } from "@/types/module";
@ -158,21 +158,21 @@ const EditDocument = (): ReactElement => {
</div>
<div>
<h3 className="text-sm font-semibold text-[#0f1724]">
Edit Document Metadata
Edit Document
</h3>
<p className="text-xs text-[#6b7280] mt-1">
Updates will apply to the current version of the document.
</p>
</div>
</div>
<button
{/* <button
type="button"
onClick={() => navigate(-1)}
className="inline-flex items-center gap-1.5 text-xs text-[#475569] hover:text-[#0f1724]"
>
<ArrowLeft className="w-3.5 h-3.5" />
Back
</button>
</button> */}
</div>
<div className="grid grid-cols-1 gap-x-4">

View File

@ -6,8 +6,9 @@ import {
FormSelect,
Modal,
PrimaryButton,
RichTextEditor,
SecondaryButton,
CustomButton,
RichTextEditor,
type Column,
} from "@/components/shared";
import {
@ -502,76 +503,65 @@ const ViewDocument = (): ReactElement => {
<div className="flex items-center gap-2">
{document?.status === "draft" && (
<div className="flex items-center gap-2">
<button
type="button"
className="inline-flex items-center gap-2 h-9 px-4 rounded-md border border-[#e2e8f0] text-[#475569] bg-white text-xs font-medium hover:bg-[#f8fafc] transition-colors"
<CustomButton
variant="secondary"
onClick={() => navigate(`/tenant/documents/edit/${id}`)}
>
Edit Metadata
</button>
<button
type="button"
className="inline-flex items-center gap-2 h-9 px-4 rounded-md text-white text-xs font-medium transition-colors hover:opacity-90"
style={{ backgroundColor: primaryColor }}
Edit Document
</CustomButton>
<CustomButton
variant="primary"
onClick={() => void openActionModal("submit")}
>
{ACTION_LABELS["submit"]}
</button>
</CustomButton>
</div>
)}
{document?.status === "in_review" && (
<>
<button
type="button"
className="inline-flex items-center gap-2 h-9 px-4 rounded-md text-white text-xs font-medium opacity-90 hover:opacity-100"
style={{ backgroundColor: primaryColor }}
<div className="flex items-center gap-2">
<CustomButton
variant="primary"
onClick={() => void openWorkflowTracker()}
>
Workflow Status
</button>
<button
type="button"
className="inline-flex items-center gap-2 h-9 px-4 rounded-md bg-emerald-600 text-white text-xs font-medium hover:bg-emerald-700"
</CustomButton>
<CustomButton
variant="success"
onClick={() => void openActionModal("approve")}
>
{ACTION_LABELS["approve"]}
</button>
<button
type="button"
className="inline-flex items-center gap-2 h-9 px-4 rounded-md bg-red-600 text-white text-xs font-medium hover:bg-red-700"
</CustomButton>
<CustomButton
variant="danger"
onClick={() => void openActionModal("reject")}
>
{ACTION_LABELS["reject"]}
</button>
</>
</CustomButton>
</div>
)}
{document?.status === "approved" && (
<div className="flex items-center gap-2">
<button
type="button"
className="inline-flex items-center gap-2 h-9 px-4 rounded-md text-white text-xs font-medium hover:opacity-90"
style={{ backgroundColor: primaryColor }}
<CustomButton
variant="primary"
onClick={() => void openActionModal("effective")}
>
{ACTION_LABELS["effective"]}
</button>
<button
type="button"
className="inline-flex items-center gap-2 h-9 px-4 rounded-md bg-red-600 text-white text-xs font-medium hover:bg-red-700"
</CustomButton>
<CustomButton
variant="danger"
onClick={() => void openActionModal("reject")}
>
{ACTION_LABELS["reject"]}
</button>
</CustomButton>
</div>
)}
{document?.status === "effective" && (
<button
type="button"
className="inline-flex items-center gap-2 h-9 px-4 rounded-md border border-red-200 text-red-600 bg-red-50 text-xs font-medium hover:bg-red-100"
<CustomButton
variant="danger"
onClick={() => void openActionModal("obsolete")}
>
{ACTION_LABELS["obsolete"]}
</button>
</CustomButton>
)}
</div>
),
@ -597,9 +587,12 @@ const ViewDocument = (): ReactElement => {
<span className="px-2 py-1 rounded-full bg-amber-100 text-amber-700 text-[11px] font-medium">
{document?.document_type || "-"}
</span>
<span
<span
className="px-2 py-1 rounded-full text-[11px] font-medium"
style={{ backgroundColor: `${primaryColor}1A`, color: primaryColor }}
style={{
backgroundColor: `${primaryColor}1A`,
color: primaryColor,
}}
>
v{document?.current_version || "-"}
</span>
@ -616,9 +609,12 @@ const ViewDocument = (): ReactElement => {
<button
type="button"
className={`text-sm pb-2 transition-colors`}
style={{
style={{
color: activeTab === "overview" ? primaryColor : "#6b7280",
borderBottom: activeTab === "overview" ? `2px solid ${primaryColor}` : "none"
borderBottom:
activeTab === "overview"
? `2px solid ${primaryColor}`
: "none",
}}
onClick={() => setActiveTab("overview")}
>
@ -627,9 +623,13 @@ const ViewDocument = (): ReactElement => {
<button
type="button"
className={`text-sm pb-2 transition-colors`}
style={{
color: activeTab === "version-history" ? primaryColor : "#6b7280",
borderBottom: activeTab === "version-history" ? `2px solid ${primaryColor}` : "none"
style={{
color:
activeTab === "version-history" ? primaryColor : "#6b7280",
borderBottom:
activeTab === "version-history"
? `2px solid ${primaryColor}`
: "none",
}}
onClick={() => setActiveTab("version-history")}
>
@ -638,9 +638,13 @@ const ViewDocument = (): ReactElement => {
<button
type="button"
className={`text-sm pb-2 transition-colors`}
style={{
color: activeTab === "workflow-history" ? primaryColor : "#6b7280",
borderBottom: activeTab === "workflow-history" ? `2px solid ${primaryColor}` : "none"
style={{
color:
activeTab === "workflow-history" ? primaryColor : "#6b7280",
borderBottom:
activeTab === "workflow-history"
? `2px solid ${primaryColor}`
: "none",
}}
onClick={() => setActiveTab("workflow-history")}
>
@ -748,7 +752,8 @@ const ViewDocument = (): ReactElement => {
Update Document Content
</h4>
<p className="text-xs text-[#6b7280] mt-1">
Modify the document content or load it from an existing file attachment.
Modify the document content or load it from an
existing file attachment.
</p>
</div>
@ -764,7 +769,8 @@ const ViewDocument = (): ReactElement => {
Load Content From File
</span>
<p className="text-xs text-[#6b7280]">
Extract text directly from an uploaded PDF or Word document.
Extract text directly from an uploaded PDF
or Word document.
</p>
</div>
</div>
@ -790,22 +796,38 @@ const ViewDocument = (): ReactElement => {
{versionSelectedFileId && (
<div className="mt-3 grid grid-cols-2 gap-x-4 gap-y-2 p-3 bg-white border border-[rgba(0,0,0,0.08)] rounded-md text-[11px]">
<div>
<span className="text-gray-400">File Name:</span>{" "}
<span className="text-[#0f1724] font-medium">{versionFileName}</span>
</div>
<div>
<span className="text-gray-400">File Size:</span>{" "}
<span className="text-gray-400">
File Name:
</span>{" "}
<span className="text-[#0f1724] font-medium">
{versionFileSize ? `${(versionFileSize / 1024 / 1024).toFixed(2)} MB` : "-"}
{versionFileName}
</span>
</div>
<div>
<span className="text-gray-400">MIME Type:</span>{" "}
<span className="text-[#0f1724] font-medium">{versionMimeType}</span>
<span className="text-gray-400">
File Size:
</span>{" "}
<span className="text-[#0f1724] font-medium">
{versionFileSize
? `${(versionFileSize / 1024 / 1024).toFixed(2)} MB`
: "-"}
</span>
</div>
<div>
<span className="text-gray-400">Checksum:</span>{" "}
<span className="text-[#0f1724] font-mono">{versionFileHash?.substring(0, 12)}...</span>
<span className="text-gray-400">
MIME Type:
</span>{" "}
<span className="text-[#0f1724] font-medium">
{versionMimeType}
</span>
</div>
<div>
<span className="text-gray-400">
Checksum:
</span>{" "}
<span className="text-[#0f1724] font-mono">
{versionFileHash?.substring(0, 12)}...
</span>
</div>
</div>
)}
@ -835,10 +857,22 @@ const ViewDocument = (): ReactElement => {
label="Change Reason"
required
options={[
{ value: "minor_edit", label: "minor_edit" },
{ value: "correction", label: "correction" },
{ value: "regulatory_update", label: "regulatory_update" },
{ value: "major_rewrite", label: "major_rewrite" },
{
value: "minor_edit",
label: "minor_edit",
},
{
value: "correction",
label: "correction",
},
{
value: "regulatory_update",
label: "regulatory_update",
},
{
value: "major_rewrite",
label: "major_rewrite",
},
]}
value={newVersionChangeReason}
onValueChange={(val) => {
@ -853,7 +887,8 @@ const ViewDocument = (): ReactElement => {
error={versionErrors.change_reason}
/>
<p className="text-[10px] text-[#6b7280]">
Select a reason category such as minor_edit, correction, etc.
Select a reason category such as minor_edit,
correction, etc.
</p>
</div>
@ -864,16 +899,22 @@ const ViewDocument = (): ReactElement => {
</label>
<button
type="button"
onClick={() => setIsMajorVersion(!isMajorVersion)}
onClick={() =>
setIsMajorVersion(!isMajorVersion)
}
className={cn(
"relative inline-flex h-5 w-9 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none",
isMajorVersion ? "bg-[#112868]" : "bg-gray-200"
isMajorVersion
? "bg-[#112868]"
: "bg-gray-200",
)}
>
<span
className={cn(
"pointer-events-none inline-block h-4 w-4 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out",
isMajorVersion ? "translate-x-4" : "translate-x-0"
isMajorVersion
? "translate-x-4"
: "translate-x-0",
)}
/>
</button>
@ -881,15 +922,24 @@ const ViewDocument = (): ReactElement => {
<div className="h-10 px-3 flex items-center justify-between bg-white border border-[rgba(0,0,0,0.08)] rounded-md shadow-sm">
<span className="text-[11px] font-medium text-[#475569]">
{isMajorVersion ? "Major" : "Minor"} Increment
{isMajorVersion ? "Major" : "Minor"}{" "}
Increment
</span>
<span className="text-[11px] font-bold text-[#0f1724]">
{`${document?.current_version || "0.0"} to ${(() => {
if (!document?.current_version) return isMajorVersion ? "1.0" : "0.1";
const parts = document.current_version.replace("v", "").split(".");
if (!document?.current_version)
return isMajorVersion ? "1.0" : "0.1";
const parts = document.current_version
.replace("v", "")
.split(".");
let major = parseInt(parts[0]) || 0;
let minor = parseInt(parts[1]) || 0;
if (isMajorVersion) { major++; minor = 0; } else { minor++; }
if (isMajorVersion) {
major++;
minor = 0;
} else {
minor++;
}
return `${major}.${minor}`;
})()}`}
</span>
@ -897,8 +947,7 @@ const ViewDocument = (): ReactElement => {
<p className="text-[10px] text-[#6b7280] leading-tight">
{isMajorVersion
? "Incrementing to a major version indicates significant changes or a full document overhaul."
: `Turn on major versioning to increment to v${(parseInt((document?.current_version || "0.0").split(".")[0]) || 0) + 1}.0 instead of a minor revision.`
}
: `Turn on major versioning to increment to v${(parseInt((document?.current_version || "0.0").split(".")[0]) || 0) + 1}.0 instead of a minor revision.`}
</p>
</div>
</div>
@ -917,7 +966,6 @@ const ViewDocument = (): ReactElement => {
className="w-full px-3 py-2 border border-[rgba(0,0,0,0.08)] rounded-lg text-sm focus:ring-1 focus:ring-[#112868]/20 focus:outline-none transition-all"
/>
</div>
</div>
<div className="p-6 py-4 border-t border-[rgba(0,0,0,0.08)] bg-gray-50/50 flex justify-end gap-3">
@ -931,7 +979,9 @@ const ViewDocument = (): ReactElement => {
disabled={isVersionSaving}
className="px-8"
>
{isVersionSaving ? "Creating..." : "Save New Version"}
{isVersionSaving
? "Creating..."
: "Save New Version"}
</PrimaryButton>
</div>
</div>
@ -942,8 +992,12 @@ const ViewDocument = (): ReactElement => {
{/* Version Context Card */}
<div className="bg-white border border-[rgba(0,0,0,0.08)] rounded-xl shadow-sm overflow-hidden">
<div className="p-5 border-b border-[rgba(0,0,0,0.08)]">
<h4 className="text-sm font-bold text-[#0f1724]">Version Context</h4>
<p className="text-[11px] text-[#6b7280] mt-0.5">Reference information for current revision.</p>
<h4 className="text-sm font-bold text-[#0f1724]">
Version Context
</h4>
<p className="text-[11px] text-[#6b7280] mt-0.5">
Reference information for current revision.
</p>
</div>
<div className="p-5 pt-4 space-y-4">
<div className="inline-flex items-center px-2.5 py-1 rounded-md bg-[#084cc8]/5 text-[#084cc8] text-[11px] font-bold border border-[#084cc8]/10 w-full justify-center">
@ -952,24 +1006,56 @@ const ViewDocument = (): ReactElement => {
<div className="space-y-3">
{[
{ label: "Document Number", value: document?.document_number },
{ label: "Current Version", value: `v${document?.current_version || "-"}` },
{ label: "Next Version", value: (() => {
if (!document?.current_version) return isMajorVersion ? "v1.0" : "v0.1";
const parts = document.current_version.replace("v", "").split(".");
{
label: "Document Number",
value: document?.document_number,
},
{
label: "Current Version",
value: `v${document?.current_version || "-"}`,
},
{
label: "Next Version",
value: (() => {
if (!document?.current_version)
return isMajorVersion ? "v1.0" : "v0.1";
const parts = document.current_version
.replace("v", "")
.split(".");
let major = parseInt(parts[0]) || 0;
let minor = parseInt(parts[1]) || 0;
if (isMajorVersion) { major++; minor = 0; } else { minor++; }
if (isMajorVersion) {
major++;
minor = 0;
} else {
minor++;
}
return `v${major}.${minor}`;
})()
})(),
},
{
label: "Document Type",
value: document?.document_type,
},
{
label: "Owner",
value: document?.owner?.name || "-",
},
{
label: "Department",
value: document?.department || "-",
},
{ label: "Document Type", value: document?.document_type },
{ label: "Owner", value: document?.owner?.name || "-" },
{ label: "Department", value: document?.department || "-" },
].map((item, i) => (
<div key={i} className="flex items-center justify-between py-2 border-b border-gray-50 last:border-0">
<span className="text-[11px] text-[#6b7280] font-medium">{item.label}</span>
<span className="text-[11px] text-[#0f1724] font-bold">{item.value}</span>
<div
key={i}
className="flex items-center justify-between py-2 border-b border-gray-50 last:border-0"
>
<span className="text-[11px] text-[#6b7280] font-medium">
{item.label}
</span>
<span className="text-[11px] text-[#0f1724] font-bold">
{item.value}
</span>
</div>
))}
</div>
@ -979,9 +1065,12 @@ const ViewDocument = (): ReactElement => {
{/* Review Notes Card */}
<div className="bg-white border border-[rgba(0,0,0,0.08)] rounded-xl shadow-sm p-5 space-y-4">
<div>
<h4 className="text-sm font-bold text-[#0f1724]">Review Notes</h4>
<h4 className="text-sm font-bold text-[#0f1724]">
Review Notes
</h4>
<p className="text-[11px] text-[#6b7280] mt-0.5 leading-relaxed">
Creating a new draft revision will automatically trigger the following system actions:
Creating a new draft revision will automatically
trigger the following system actions:
</p>
</div>
@ -989,11 +1078,16 @@ const ViewDocument = (): ReactElement => {
{[
"Status resets to Draft",
"Workflow restarts",
"Version history retained"
"Version history retained",
].map((note, i) => (
<div key={i} className="flex items-center gap-2 px-3 py-2 bg-gray-50 rounded-lg border border-gray-100/50">
<div
key={i}
className="flex items-center gap-2 px-3 py-2 bg-gray-50 rounded-lg border border-gray-100/50"
>
<div className="w-1.5 h-1.5 rounded-full bg-[#112868]/40" />
<span className="text-[11px] font-semibold text-[#475569]">{note}</span>
<span className="text-[11px] font-semibold text-[#475569]">
{note}
</span>
</div>
))}
</div>
@ -1354,7 +1448,9 @@ const ViewDocument = (): ReactElement => {
<div className="p-4 border border-amber-200 bg-amber-50 rounded-lg animate-in fade-in slide-in-from-top-2">
<div className="flex justify-between items-start mb-3">
<h4 className="text-xs font-bold text-amber-900 uppercase tracking-wider">
Executing Action: {selectedWorkflowAction.action||selectedWorkflowAction.name}
Executing Action:{" "}
{selectedWorkflowAction.action ||
selectedWorkflowAction.name}
</h4>
<button
onClick={() => {