Styles styels styles
This commit is contained in:
@@ -1,5 +1,26 @@
|
|||||||
# Frontend Complete Refactoring & Standardization Plan
|
# Frontend Complete Refactoring & Standardization Plan
|
||||||
|
|
||||||
|
## ✅ STATUS: TAILWIND DEFAULT COLORS DISABLED
|
||||||
|
|
||||||
|
**As of 2026-01-01**: All Tailwind default color palettes (blue-500, red-600, green-400, etc.) have been **disabled** via `@theme` in `design-system.css`. Only our custom design system colors work now.
|
||||||
|
|
||||||
|
### What's Disabled (via `@theme { --color-*-*: initial; }`)
|
||||||
|
- `red-*`, `orange-*`, `amber-*`, `yellow-*`, `lime-*`
|
||||||
|
- `green-*`, `emerald-*`, `teal-*`, `cyan-*`, `sky-*`
|
||||||
|
- `blue-*`, `indigo-*`, `violet-*`, `fuchsia-*`, `pink-*`, `rose-*`
|
||||||
|
- `slate-*`, `zinc-*`, `neutral-*`, `stone-*`
|
||||||
|
|
||||||
|
### What WORKS (our design system)
|
||||||
|
- `brand-*` (25-950) - derived from `--color-primary`
|
||||||
|
- `success-*` (25-950) - derived from `--color-success`
|
||||||
|
- `warning-*` (25-950) - derived from `--color-warning`
|
||||||
|
- `error-*` (25-950) - derived from `--color-danger`
|
||||||
|
- `purple-*` (25-950) - derived from `--color-purple`
|
||||||
|
- `gray-*` (25-950) - derived from `--color-gray-base`
|
||||||
|
- `blue-light-*` - info/link colors (derived from primary)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 🎯 Objective
|
## 🎯 Objective
|
||||||
|
|
||||||
**Completely remove Tailwind default styles** and refactor the entire frontend to use **ONLY** the custom design system defined in `/src/styles/design-system.css`.
|
**Completely remove Tailwind default styles** and refactor the entire frontend to use **ONLY** the custom design system defined in `/src/styles/design-system.css`.
|
||||||
|
|||||||
108
frontend/package-lock.json
generated
108
frontend/package-lock.json
generated
@@ -43,6 +43,7 @@
|
|||||||
"@types/react-dom": "^19.0.4",
|
"@types/react-dom": "^19.0.4",
|
||||||
"@vitejs/plugin-react": "^4.3.4",
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
"@vitest/ui": "^1.0.4",
|
"@vitest/ui": "^1.0.4",
|
||||||
|
"autoprefixer": "^10.4.23",
|
||||||
"eslint": "^9.19.0",
|
"eslint": "^9.19.0",
|
||||||
"eslint-plugin-react-hooks": "^5.0.0",
|
"eslint-plugin-react-hooks": "^5.0.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.18",
|
"eslint-plugin-react-refresh": "^0.4.18",
|
||||||
@@ -3058,6 +3059,43 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/autoprefixer": {
|
||||||
|
"version": "10.4.23",
|
||||||
|
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz",
|
||||||
|
"integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/postcss/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "tidelift",
|
||||||
|
"url": "https://tidelift.com/funding/github/npm/autoprefixer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"browserslist": "^4.28.1",
|
||||||
|
"caniuse-lite": "^1.0.30001760",
|
||||||
|
"fraction.js": "^5.3.4",
|
||||||
|
"picocolors": "^1.1.1",
|
||||||
|
"postcss-value-parser": "^4.2.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"autoprefixer": "bin/autoprefixer"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^10 || ^12 || >=14"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"postcss": "^8.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/available-typed-arrays": {
|
"node_modules/available-typed-arrays": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
|
||||||
@@ -3081,6 +3119,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/baseline-browser-mapping": {
|
||||||
|
"version": "2.9.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz",
|
||||||
|
"integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"baseline-browser-mapping": "dist/cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/bidi-js": {
|
"node_modules/bidi-js": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
|
||||||
@@ -3116,9 +3164,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/browserslist": {
|
"node_modules/browserslist": {
|
||||||
"version": "4.24.4",
|
"version": "4.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz",
|
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
|
||||||
"integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==",
|
"integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -3136,10 +3184,11 @@
|
|||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"caniuse-lite": "^1.0.30001688",
|
"baseline-browser-mapping": "^2.9.0",
|
||||||
"electron-to-chromium": "^1.5.73",
|
"caniuse-lite": "^1.0.30001759",
|
||||||
"node-releases": "^2.0.19",
|
"electron-to-chromium": "^1.5.263",
|
||||||
"update-browserslist-db": "^1.1.1"
|
"node-releases": "^2.0.27",
|
||||||
|
"update-browserslist-db": "^1.2.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"browserslist": "cli.js"
|
"browserslist": "cli.js"
|
||||||
@@ -3232,9 +3281,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001699",
|
"version": "1.0.30001762",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001699.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001762.tgz",
|
||||||
"integrity": "sha512-b+uH5BakXZ9Do9iK+CkDmctUSEqZl+SP056vc5usa0PL+ev5OHw003rZXcnjNDv3L8P5j6rwT6C0BPKSikW08w==",
|
"integrity": "sha512-PxZwGNvH7Ak8WX5iXzoK1KPZttBXNPuaOvI2ZYU7NrlM+d9Ov+TUvlLOBNGzVXAntMSMMlJPd+jY6ovrVjSmUw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -3676,9 +3725,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.5.98",
|
"version": "1.5.267",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.98.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz",
|
||||||
"integrity": "sha512-bI/LbtRBxU2GzK7KK5xxFd2y9Lf9XguHooPYbcXWy6wUoT8NMnffsvRhPmSeUHLSDKAEtKuTaEtK4Ms15zkIEA==",
|
"integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
@@ -4243,6 +4292,20 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fraction.js": {
|
||||||
|
"version": "5.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
|
||||||
|
"integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/rawify"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fsevents": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
@@ -5623,9 +5686,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/node-releases": {
|
"node_modules/node-releases": {
|
||||||
"version": "2.0.19",
|
"version": "2.0.27",
|
||||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
|
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
|
||||||
"integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
|
"integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
@@ -5977,6 +6040,13 @@
|
|||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/postcss-value-parser": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/preact": {
|
"node_modules/preact": {
|
||||||
"version": "10.12.1",
|
"version": "10.12.1",
|
||||||
"resolved": "https://registry.npmjs.org/preact/-/preact-10.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/preact/-/preact-10.12.1.tgz",
|
||||||
@@ -7007,9 +7077,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/update-browserslist-db": {
|
"node_modules/update-browserslist-db": {
|
||||||
"version": "1.1.2",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
|
||||||
"integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==",
|
"integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -52,6 +52,7 @@
|
|||||||
"@types/react-dom": "^19.0.4",
|
"@types/react-dom": "^19.0.4",
|
||||||
"@vitejs/plugin-react": "^4.3.4",
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
"@vitest/ui": "^1.0.4",
|
"@vitest/ui": "^1.0.4",
|
||||||
|
"autoprefixer": "^10.4.23",
|
||||||
"eslint": "^9.19.0",
|
"eslint": "^9.19.0",
|
||||||
"eslint-plugin-react-hooks": "^5.0.0",
|
"eslint-plugin-react-hooks": "^5.0.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.18",
|
"eslint-plugin-react-refresh": "^0.4.18",
|
||||||
|
|||||||
@@ -272,7 +272,7 @@ export default function StandardThreeWidgetFooter({
|
|||||||
const moduleType: ModuleType = module || (moduleStats?.title?.toLowerCase().includes('planner') ? 'planner' : 'writer');
|
const moduleType: ModuleType = module || (moduleStats?.title?.toLowerCase().includes('planner') ? 'planner' : 'writer');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`mt-8 pt-6 border-t border-gray-200 dark:border-gray-700 w-full max-w-full overflow-hidden ${className}`}>
|
<div className={`pt-4 border-t border-gray-200 dark:border-gray-700 w-full max-w-full overflow-hidden ${className}`}>
|
||||||
{/*
|
{/*
|
||||||
* Widget widths adjusted per requirements:
|
* Widget widths adjusted per requirements:
|
||||||
* - Widget 1 (Page Progress): 28.3% (reduced by 5%)
|
* - Widget 1 (Page Progress): 28.3% (reduced by 5%)
|
||||||
|
|||||||
@@ -367,7 +367,7 @@ export default function ThreeWidgetFooter({
|
|||||||
className = '',
|
className = '',
|
||||||
}: ThreeWidgetFooterProps) {
|
}: ThreeWidgetFooterProps) {
|
||||||
return (
|
return (
|
||||||
<div className={`mt-8 pt-6 border-t border-gray-200 dark:border-gray-700 ${className}`}>
|
<div className={`pt-4 border-t border-gray-200 dark:border-gray-700 ${className}`}>
|
||||||
{/*
|
{/*
|
||||||
* Widget widths adjusted per requirements:
|
* Widget widths adjusted per requirements:
|
||||||
* - Widget 1 (Page Progress): 28.3% (reduced by 5%)
|
* - Widget 1 (Page Progress): 28.3% (reduced by 5%)
|
||||||
|
|||||||
@@ -191,8 +191,8 @@ export default function OnboardingWizard({ onComplete, onSkip }: OnboardingWizar
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4">
|
<div className="w-full max-w-3xl mx-auto">
|
||||||
<Card className="w-full max-w-2xl bg-white dark:bg-gray-900 rounded-2xl shadow-2xl overflow-hidden">
|
<Card className="w-full bg-white dark:bg-gray-900 rounded-2xl shadow-lg overflow-hidden">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="px-6 py-4 border-b border-gray-200 dark:border-gray-700">
|
<div className="px-6 py-4 border-b border-gray-200 dark:border-gray-700">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
|
|||||||
@@ -105,9 +105,9 @@ const toneStyles: Record<
|
|||||||
};
|
};
|
||||||
|
|
||||||
const sizeClasses: Record<BadgeSize, string> = {
|
const sizeClasses: Record<BadgeSize, string> = {
|
||||||
xs: "min-h-[20px] px-2.5 py-1 text-[11px] leading-[1.4]",
|
xs: "min-h-[20px] px-2.5 py-1 leading-[1.4]",
|
||||||
sm: "min-h-[24px] px-3 py-1 text-xs leading-[1.4]",
|
sm: "min-h-[24px] px-3 py-1 leading-[1.4]",
|
||||||
md: "min-h-[28px] px-3.5 py-1.5 text-sm leading-[1.4]",
|
md: "min-h-[28px] px-3.5 py-1.5 leading-[1.4]",
|
||||||
};
|
};
|
||||||
|
|
||||||
const Badge: React.FC<BadgeProps> = ({
|
const Badge: React.FC<BadgeProps> = ({
|
||||||
|
|||||||
@@ -35,11 +35,12 @@ interface TableCellProps {
|
|||||||
children: ReactNode; // Cell content
|
children: ReactNode; // Cell content
|
||||||
isHeader?: boolean; // If true, renders as <th>, otherwise <td>
|
isHeader?: boolean; // If true, renders as <th>, otherwise <td>
|
||||||
className?: string; // Optional className for styling
|
className?: string; // Optional className for styling
|
||||||
|
style?: React.CSSProperties; // Optional inline styles for width, fontSize, etc.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Table Component
|
// Table Component
|
||||||
const Table: React.FC<TableProps> = ({ children, className }) => {
|
const Table: React.FC<TableProps> = ({ children, className }) => {
|
||||||
return <table className={`min-w-full w-full ${className}`}>{children}</table>;
|
return <table className={`min-w-full w-full ${className}`} style={{ tableLayout: 'auto' }}>{children}</table>;
|
||||||
};
|
};
|
||||||
|
|
||||||
// TableHeader Component
|
// TableHeader Component
|
||||||
@@ -62,9 +63,10 @@ const TableCell: React.FC<TableCellProps> = ({
|
|||||||
children,
|
children,
|
||||||
isHeader = false,
|
isHeader = false,
|
||||||
className,
|
className,
|
||||||
|
style,
|
||||||
}) => {
|
}) => {
|
||||||
const CellTag = isHeader ? "th" : "td";
|
const CellTag = isHeader ? "th" : "td";
|
||||||
return <CellTag className={` ${className}`}>{children}</CellTag>;
|
return <CellTag className={` ${className}`} style={style}>{children}</CellTag>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export { Table, TableHeader, TableBody, TableRow, TableCell };
|
export { Table, TableHeader, TableBody, TableRow, TableCell };
|
||||||
|
|||||||
@@ -62,17 +62,18 @@ export function createApprovedPageConfig(params: {
|
|||||||
label: 'Content Idea Title',
|
label: 'Content Idea Title',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
sortField: 'title',
|
sortField: 'title',
|
||||||
|
width: '400px',
|
||||||
render: (value: string, row: Content) => (
|
render: (value: string, row: Content) => (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{params.onRowClick ? (
|
{params.onRowClick ? (
|
||||||
<button
|
<button
|
||||||
onClick={() => params.onRowClick!(row)}
|
onClick={() => params.onRowClick!(row)}
|
||||||
className="text-base font-light text-brand-500 hover:text-brand-600 hover:underline text-left transition-colors"
|
className="text-sm text-brand-500 hover:text-brand-600 hover:underline text-left transition-colors"
|
||||||
>
|
>
|
||||||
{value || `Content #${row.id}`}
|
{value || `Content #${row.id}`}
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<span className="text-base font-light text-gray-900 dark:text-white">
|
<span className="text-sm text-gray-900 dark:text-white">
|
||||||
{value || `Content #${row.id}`}
|
{value || `Content #${row.id}`}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -95,7 +96,6 @@ export function createApprovedPageConfig(params: {
|
|||||||
label: 'Status',
|
label: 'Status',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
sortField: 'status',
|
sortField: 'status',
|
||||||
width: '130px',
|
|
||||||
render: (value: string, row: Content) => {
|
render: (value: string, row: Content) => {
|
||||||
// Map internal status to user-friendly labels
|
// Map internal status to user-friendly labels
|
||||||
const statusConfig: Record<string, { color: 'success' | 'blue' | 'amber' | 'gray'; label: string }> = {
|
const statusConfig: Record<string, { color: 'success' | 'blue' | 'amber' | 'gray'; label: string }> = {
|
||||||
@@ -251,8 +251,8 @@ export function createApprovedPageConfig(params: {
|
|||||||
sortable: false, // Backend doesn't support sorting by word_count
|
sortable: false, // Backend doesn't support sorting by word_count
|
||||||
sortField: 'word_count',
|
sortField: 'word_count',
|
||||||
numeric: true,
|
numeric: true,
|
||||||
width: '100px',
|
align: 'center' as const,
|
||||||
align: 'right' as const,
|
headingAlign: 'center' as const,
|
||||||
render: (value: number) => (
|
render: (value: number) => (
|
||||||
<span className="text-gray-900 dark:text-white">
|
<span className="text-gray-900 dark:text-white">
|
||||||
{value ? value.toLocaleString() : '-'}
|
{value ? value.toLocaleString() : '-'}
|
||||||
@@ -265,7 +265,8 @@ export function createApprovedPageConfig(params: {
|
|||||||
sortable: true,
|
sortable: true,
|
||||||
sortField: 'created_at',
|
sortField: 'created_at',
|
||||||
date: true,
|
date: true,
|
||||||
width: '140px',
|
width: '130px',
|
||||||
|
hasActions: true,
|
||||||
render: (value: string) => (
|
render: (value: string) => (
|
||||||
<span className="text-gray-600 dark:text-gray-400">
|
<span className="text-gray-600 dark:text-gray-400">
|
||||||
{formatRelativeDate(value)}
|
{formatRelativeDate(value)}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
sectorColumn,
|
sectorColumn,
|
||||||
difficultyColumn,
|
difficultyColumn,
|
||||||
statusColumn,
|
statusColumn,
|
||||||
createdColumn,
|
createdWithActionsColumn,
|
||||||
} from '../snippets/columns.snippets';
|
} from '../snippets/columns.snippets';
|
||||||
import Badge from '../../components/ui/badge/Badge';
|
import Badge from '../../components/ui/badge/Badge';
|
||||||
import { formatRelativeDate } from '../../utils/date';
|
import { formatRelativeDate } from '../../utils/date';
|
||||||
@@ -107,6 +107,7 @@ export const createClustersPageConfig = (
|
|||||||
label: 'Cluster Name',
|
label: 'Cluster Name',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
sortField: 'name',
|
sortField: 'name',
|
||||||
|
width: '400px',
|
||||||
render: (value: string, row: Cluster) => (
|
render: (value: string, row: Cluster) => (
|
||||||
<Link
|
<Link
|
||||||
to={`/planner/clusters/${row.id}`}
|
to={`/planner/clusters/${row.id}`}
|
||||||
@@ -130,17 +131,8 @@ export const createClustersPageConfig = (
|
|||||||
label: 'KW Count',
|
label: 'KW Count',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
sortField: 'keywords_count',
|
sortField: 'keywords_count',
|
||||||
width: '120px',
|
|
||||||
align: 'center' as const,
|
|
||||||
render: (value: number) => value.toLocaleString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'ideas_count',
|
|
||||||
label: 'Ideas',
|
|
||||||
sortable: false, // Backend doesn't support sorting by ideas_count
|
|
||||||
sortField: 'ideas_count',
|
|
||||||
width: '120px',
|
|
||||||
align: 'center' as const,
|
align: 'center' as const,
|
||||||
|
headingAlign: 'center' as const,
|
||||||
render: (value: number) => value.toLocaleString(),
|
render: (value: number) => value.toLocaleString(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -148,8 +140,8 @@ export const createClustersPageConfig = (
|
|||||||
label: 'Volume',
|
label: 'Volume',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
sortField: 'volume',
|
sortField: 'volume',
|
||||||
width: '120px',
|
|
||||||
align: 'center' as const,
|
align: 'center' as const,
|
||||||
|
headingAlign: 'center' as const,
|
||||||
render: (value: number) => value.toLocaleString(),
|
render: (value: number) => value.toLocaleString(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -159,6 +151,7 @@ export const createClustersPageConfig = (
|
|||||||
sortable: true,
|
sortable: true,
|
||||||
sortField: 'difficulty',
|
sortField: 'difficulty',
|
||||||
align: 'center' as const,
|
align: 'center' as const,
|
||||||
|
headingAlign: 'center' as const,
|
||||||
render: (value: number) => {
|
render: (value: number) => {
|
||||||
const difficultyNum = getDifficultyNumber(value);
|
const difficultyNum = getDifficultyNumber(value);
|
||||||
const difficultyBadgeColor =
|
const difficultyBadgeColor =
|
||||||
@@ -187,8 +180,8 @@ export const createClustersPageConfig = (
|
|||||||
label: 'Content',
|
label: 'Content',
|
||||||
sortable: false, // Backend doesn't support sorting by content_count
|
sortable: false, // Backend doesn't support sorting by content_count
|
||||||
sortField: 'content_count',
|
sortField: 'content_count',
|
||||||
width: '120px',
|
|
||||||
align: 'center' as const,
|
align: 'center' as const,
|
||||||
|
headingAlign: 'center' as const,
|
||||||
render: (value: number) => value.toLocaleString(),
|
render: (value: number) => value.toLocaleString(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -205,9 +198,44 @@ export const createClustersPageConfig = (
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...createdColumn,
|
key: 'ideas_count',
|
||||||
|
label: 'Ideas',
|
||||||
|
sortable: false, // Backend doesn't support sorting by ideas_count
|
||||||
|
sortField: 'ideas_count',
|
||||||
|
align: 'center' as const,
|
||||||
|
headingAlign: 'center' as const,
|
||||||
|
render: (value: number) => value.toLocaleString(),
|
||||||
|
},
|
||||||
|
// Generate Ideas action column - only shows button for status = 'new'
|
||||||
|
{
|
||||||
|
key: 'generate_action',
|
||||||
|
label: 'Generate Ideas',
|
||||||
|
sortable: false,
|
||||||
|
render: (_value: any, row: Cluster) => {
|
||||||
|
// Only show generate button for clusters with status 'new'
|
||||||
|
if (row.status === 'new' && handlers.onGenerateIdeas) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handlers.onGenerateIdeas!(row.id);
|
||||||
|
}}
|
||||||
|
className="inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium text-white bg-brand-500 hover:bg-brand-600 rounded transition-colors"
|
||||||
|
title="Generate Ideas"
|
||||||
|
>
|
||||||
|
<BoltIcon className="w-4 h-4" />
|
||||||
|
Generate
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <span className="text-gray-400 dark:text-gray-500 text-sm">-</span>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...createdWithActionsColumn,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
sortField: 'created_at',
|
sortField: 'created_at',
|
||||||
|
width: '130px',
|
||||||
render: (value: string) => formatRelativeDate(value),
|
render: (value: string) => formatRelativeDate(value),
|
||||||
},
|
},
|
||||||
// Optional columns - hidden by default
|
// Optional columns - hidden by default
|
||||||
@@ -240,32 +268,6 @@ export const createClustersPageConfig = (
|
|||||||
defaultVisible: false,
|
defaultVisible: false,
|
||||||
render: (value: string) => formatRelativeDate(value),
|
render: (value: string) => formatRelativeDate(value),
|
||||||
},
|
},
|
||||||
// Generate Ideas action column - only shows button for status = 'new'
|
|
||||||
{
|
|
||||||
key: 'generate_action',
|
|
||||||
label: 'Generate Ideas',
|
|
||||||
sortable: false,
|
|
||||||
width: '120px',
|
|
||||||
render: (_value: any, row: Cluster) => {
|
|
||||||
// Only show generate button for clusters with status 'new'
|
|
||||||
if (row.status === 'new' && handlers.onGenerateIdeas) {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
handlers.onGenerateIdeas!(row.id);
|
|
||||||
}}
|
|
||||||
className="inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium text-white bg-brand-500 hover:bg-brand-600 rounded transition-colors"
|
|
||||||
title="Generate Ideas"
|
|
||||||
>
|
|
||||||
<BoltIcon className="w-4 h-4" />
|
|
||||||
Generate
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return <span className="text-gray-400 dark:text-gray-500 text-sm">-</span>;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import React from 'react';
|
|||||||
import {
|
import {
|
||||||
titleColumn,
|
titleColumn,
|
||||||
statusColumn,
|
statusColumn,
|
||||||
createdColumn,
|
createdWithActionsColumn,
|
||||||
wordCountColumn,
|
wordCountColumn,
|
||||||
sectorColumn,
|
sectorColumn,
|
||||||
} from '../snippets/columns.snippets';
|
} from '../snippets/columns.snippets';
|
||||||
@@ -100,17 +100,18 @@ export const createContentPageConfig = (
|
|||||||
label: 'Content Idea Title',
|
label: 'Content Idea Title',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
sortField: 'title',
|
sortField: 'title',
|
||||||
|
width: '400px',
|
||||||
render: (value: string, row: Content) => (
|
render: (value: string, row: Content) => (
|
||||||
<div>
|
<div>
|
||||||
{handlers.onRowClick ? (
|
{handlers.onRowClick ? (
|
||||||
<button
|
<button
|
||||||
onClick={() => handlers.onRowClick!(row)}
|
onClick={() => handlers.onRowClick!(row)}
|
||||||
className="text-base font-light text-brand-500 hover:text-brand-600 hover:underline text-left transition-colors"
|
className="text-sm text-brand-500 hover:text-brand-600 hover:underline text-left transition-colors"
|
||||||
>
|
>
|
||||||
{row.title || `Content #${row.id}`}
|
{row.title || `Content #${row.id}`}
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<div className="text-base font-light text-gray-900 dark:text-white">
|
<div className="text-sm text-gray-900 dark:text-white">
|
||||||
{row.title || `Content #${row.id}`}
|
{row.title || `Content #${row.id}`}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -130,7 +131,6 @@ export const createContentPageConfig = (
|
|||||||
label: 'Type',
|
label: 'Type',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
sortField: 'content_type',
|
sortField: 'content_type',
|
||||||
width: '110px',
|
|
||||||
render: (value: string) => {
|
render: (value: string) => {
|
||||||
const label = TYPE_LABELS[value] || value || '-';
|
const label = TYPE_LABELS[value] || value || '-';
|
||||||
// Proper case: capitalize first letter only
|
// Proper case: capitalize first letter only
|
||||||
@@ -147,7 +147,6 @@ export const createContentPageConfig = (
|
|||||||
label: 'Structure',
|
label: 'Structure',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
sortField: 'content_structure',
|
sortField: 'content_structure',
|
||||||
width: '130px',
|
|
||||||
render: (value: string) => {
|
render: (value: string) => {
|
||||||
const label = STRUCTURE_LABELS[value] || value || '-';
|
const label = STRUCTURE_LABELS[value] || value || '-';
|
||||||
// Proper case: capitalize first letter of each word
|
// Proper case: capitalize first letter of each word
|
||||||
@@ -165,16 +164,15 @@ export const createContentPageConfig = (
|
|||||||
key: 'cluster_name',
|
key: 'cluster_name',
|
||||||
label: 'Cluster',
|
label: 'Cluster',
|
||||||
sortable: false,
|
sortable: false,
|
||||||
width: '130px',
|
|
||||||
render: (_value: any, row: Content) => {
|
render: (_value: any, row: Content) => {
|
||||||
const clusterName = row.cluster_name;
|
const clusterName = row.cluster_name;
|
||||||
if (!clusterName) {
|
if (!clusterName) {
|
||||||
return <span className="text-gray-400 dark:text-gray-500 text-[11px]">-</span>;
|
return <span className="text-gray-400 dark:text-gray-500">-</span>;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Badge color="indigo" size="xs" variant="soft">
|
<span className="text-gray-800 dark:text-white">
|
||||||
<span className="text-[11px] font-normal">{clusterName}</span>
|
{clusterName}
|
||||||
</Badge>
|
</span>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -305,11 +303,11 @@ export const createContentPageConfig = (
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...createdColumn,
|
...createdWithActionsColumn,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
sortField: 'created_at',
|
sortField: 'created_at',
|
||||||
label: 'Created',
|
label: 'Created',
|
||||||
align: 'right',
|
width: '130px',
|
||||||
render: (value: string, row: Content) => {
|
render: (value: string, row: Content) => {
|
||||||
// Prompt icon logic (unchanged)
|
// Prompt icon logic (unchanged)
|
||||||
const hasPrompts = row.has_image_prompts || false;
|
const hasPrompts = row.has_image_prompts || false;
|
||||||
@@ -335,7 +333,7 @@ export const createContentPageConfig = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-end gap-3 pr-10">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-gray-700 dark:text-gray-300 whitespace-nowrap">
|
<span className="text-gray-700 dark:text-gray-300 whitespace-nowrap">
|
||||||
{formatRelativeDate(value)}
|
{formatRelativeDate(value)}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
titleColumn,
|
titleColumn,
|
||||||
sectorColumn,
|
sectorColumn,
|
||||||
statusColumn,
|
statusColumn,
|
||||||
createdColumn,
|
createdWithActionsColumn,
|
||||||
} from '../snippets/columns.snippets';
|
} from '../snippets/columns.snippets';
|
||||||
import Badge from '../../components/ui/badge/Badge';
|
import Badge from '../../components/ui/badge/Badge';
|
||||||
import { formatRelativeDate } from '../../utils/date';
|
import { formatRelativeDate } from '../../utils/date';
|
||||||
@@ -98,6 +98,7 @@ export const createIdeasPageConfig = (
|
|||||||
label: 'Content Idea Title',
|
label: 'Content Idea Title',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
sortField: 'idea_title',
|
sortField: 'idea_title',
|
||||||
|
width: '400px',
|
||||||
toggleable: true, // Enable toggle for this column
|
toggleable: true, // Enable toggle for this column
|
||||||
toggleContentKey: 'description', // Use description field for toggle content
|
toggleContentKey: 'description', // Use description field for toggle content
|
||||||
toggleContentLabel: 'Content Outline', // Label for expanded content
|
toggleContentLabel: 'Content Outline', // Label for expanded content
|
||||||
@@ -119,7 +120,6 @@ export const createIdeasPageConfig = (
|
|||||||
label: 'Structure',
|
label: 'Structure',
|
||||||
sortable: false, // Backend doesn't support sorting by content_structure
|
sortable: false, // Backend doesn't support sorting by content_structure
|
||||||
sortField: 'content_structure',
|
sortField: 'content_structure',
|
||||||
width: '130px',
|
|
||||||
render: (value: string) => {
|
render: (value: string) => {
|
||||||
const label = value?.replace('_', ' ') || '-';
|
const label = value?.replace('_', ' ') || '-';
|
||||||
const properCase = label.split(/[_\s]+/).map(word =>
|
const properCase = label.split(/[_\s]+/).map(word =>
|
||||||
@@ -137,7 +137,6 @@ export const createIdeasPageConfig = (
|
|||||||
label: 'Type',
|
label: 'Type',
|
||||||
sortable: false, // Backend doesn't support sorting by content_type
|
sortable: false, // Backend doesn't support sorting by content_type
|
||||||
sortField: 'content_type',
|
sortField: 'content_type',
|
||||||
width: '110px',
|
|
||||||
render: (value: string) => {
|
render: (value: string) => {
|
||||||
const label = value?.replace('_', ' ') || '-';
|
const label = value?.replace('_', ' ') || '-';
|
||||||
const properCase = label.charAt(0).toUpperCase() + label.slice(1);
|
const properCase = label.charAt(0).toUpperCase() + label.slice(1);
|
||||||
@@ -152,9 +151,8 @@ export const createIdeasPageConfig = (
|
|||||||
key: 'target_keywords',
|
key: 'target_keywords',
|
||||||
label: 'Keywords',
|
label: 'Keywords',
|
||||||
sortable: false,
|
sortable: false,
|
||||||
width: '250px',
|
|
||||||
render: (value: string) => (
|
render: (value: string) => (
|
||||||
<span className="text-sm text-gray-600 dark:text-gray-400 truncate block max-w-[250px]">
|
<span className="text-sm text-gray-600 dark:text-gray-400">
|
||||||
{value || '-'}
|
{value || '-'}
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
@@ -164,7 +162,6 @@ export const createIdeasPageConfig = (
|
|||||||
label: 'Cluster',
|
label: 'Cluster',
|
||||||
sortable: false, // Backend doesn't support sorting by keyword_cluster_id
|
sortable: false, // Backend doesn't support sorting by keyword_cluster_id
|
||||||
sortField: 'keyword_cluster_id',
|
sortField: 'keyword_cluster_id',
|
||||||
width: '200px',
|
|
||||||
render: (_value: string, row: ContentIdea) => {
|
render: (_value: string, row: ContentIdea) => {
|
||||||
if (row.keyword_cluster_id && row.keyword_cluster_name) {
|
if (row.keyword_cluster_id && row.keyword_cluster_name) {
|
||||||
return (
|
return (
|
||||||
@@ -202,14 +199,15 @@ export const createIdeasPageConfig = (
|
|||||||
label: 'Words',
|
label: 'Words',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
sortField: 'estimated_word_count',
|
sortField: 'estimated_word_count',
|
||||||
width: '100px',
|
|
||||||
align: 'center' as const,
|
align: 'center' as const,
|
||||||
|
headingAlign: 'center' as const,
|
||||||
render: (value: number) => value.toLocaleString(),
|
render: (value: number) => value.toLocaleString(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...createdColumn,
|
...createdWithActionsColumn,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
sortField: 'created_at',
|
sortField: 'created_at',
|
||||||
|
width: '130px',
|
||||||
render: (value: string) => formatRelativeDate(value),
|
render: (value: string) => formatRelativeDate(value),
|
||||||
},
|
},
|
||||||
// Optional columns - hidden by default
|
// Optional columns - hidden by default
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export const createImagesPageConfig = (
|
|||||||
label: 'Content Title',
|
label: 'Content Title',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
sortField: 'content_title',
|
sortField: 'content_title',
|
||||||
width: '250px',
|
width: '400px',
|
||||||
render: (_value: string, row: ContentImagesGroup) => {
|
render: (_value: string, row: ContentImagesGroup) => {
|
||||||
const statusColors: Record<string, 'warning' | 'info' | 'success'> = {
|
const statusColors: Record<string, 'warning' | 'info' | 'success'> = {
|
||||||
draft: 'warning',
|
draft: 'warning',
|
||||||
@@ -130,13 +130,14 @@ export const createImagesPageConfig = (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add status column (separate from generate button)
|
// Add status column with actions (3-dot dropdown rendered by TablePageTemplate)
|
||||||
columns.push({
|
columns.push({
|
||||||
key: 'overall_status',
|
key: 'overall_status',
|
||||||
label: 'Status',
|
label: 'Status',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
sortField: 'overall_status',
|
sortField: 'overall_status',
|
||||||
width: '120px',
|
width: '130px',
|
||||||
|
hasActions: true,
|
||||||
render: (value: string, row: ContentImagesGroup) => {
|
render: (value: string, row: ContentImagesGroup) => {
|
||||||
const statusColors: Record<string, 'success' | 'warning' | 'error' | 'info'> = {
|
const statusColors: Record<string, 'success' | 'warning' | 'error' | 'info'> = {
|
||||||
'complete': 'success',
|
'complete': 'success',
|
||||||
@@ -159,40 +160,6 @@ export const createImagesPageConfig = (
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add generate button column (separate from status)
|
|
||||||
columns.push({
|
|
||||||
key: 'generate_action',
|
|
||||||
label: 'Actions',
|
|
||||||
sortable: false,
|
|
||||||
width: '120px',
|
|
||||||
render: (_value: any, row: ContentImagesGroup) => {
|
|
||||||
// Check if there are any pending images with prompts
|
|
||||||
const hasPendingImages =
|
|
||||||
(row.featured_image?.status === 'pending' && row.featured_image?.prompt) ||
|
|
||||||
row.in_article_images.some(img => img.status === 'pending' && img.prompt);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{hasPendingImages && handlers.onGenerateImages ? (
|
|
||||||
<button
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
handlers.onGenerateImages!(row.content_id);
|
|
||||||
}}
|
|
||||||
className="inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium text-white bg-brand-500 hover:bg-brand-600 rounded transition-colors"
|
|
||||||
title="Generate Images"
|
|
||||||
>
|
|
||||||
<BoltIcon className="w-4 h-4" />
|
|
||||||
Generate
|
|
||||||
</button>
|
|
||||||
) : (
|
|
||||||
<span className="text-gray-400 dark:text-gray-500 text-sm">-</span>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
columns,
|
columns,
|
||||||
filters: [
|
filters: [
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
clusterColumn,
|
clusterColumn,
|
||||||
sectorColumn,
|
sectorColumn,
|
||||||
statusColumn,
|
statusColumn,
|
||||||
createdColumn,
|
createdWithActionsColumn,
|
||||||
} from '../snippets/columns.snippets';
|
} from '../snippets/columns.snippets';
|
||||||
// Icons removed - bulkActions and rowActions are now in table-actions.config.tsx
|
// Icons removed - bulkActions and rowActions are now in table-actions.config.tsx
|
||||||
import Badge from '../../components/ui/badge/Badge';
|
import Badge from '../../components/ui/badge/Badge';
|
||||||
@@ -146,13 +146,19 @@ export const createKeywordsPageConfig = (
|
|||||||
...keywordColumn,
|
...keywordColumn,
|
||||||
sortable: false, // Backend doesn't support sorting by keyword field
|
sortable: false, // Backend doesn't support sorting by keyword field
|
||||||
sortField: 'seed_keyword__keyword',
|
sortField: 'seed_keyword__keyword',
|
||||||
|
width: '300px',
|
||||||
|
render: (value: string) => (
|
||||||
|
<span>
|
||||||
|
{value || '-'}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
// Sector column - only show when viewing all sectors
|
// Sector column - only show when viewing all sectors
|
||||||
...(showSectorColumn ? [{
|
...(showSectorColumn ? [{
|
||||||
...sectorColumn,
|
...sectorColumn,
|
||||||
render: (value: string, row: Keyword) => (
|
render: (value: string, row: Keyword) => (
|
||||||
<Badge color="info" size="xs" variant="soft">
|
<Badge color="info" size="xs" variant="soft">
|
||||||
<span className="text-[11px] font-normal">{row.sector_name || '-'}</span>
|
{row.sector_name || '-'}
|
||||||
</Badge>
|
</Badge>
|
||||||
),
|
),
|
||||||
}] : []),
|
}] : []),
|
||||||
@@ -161,16 +167,18 @@ export const createKeywordsPageConfig = (
|
|||||||
sortable: true,
|
sortable: true,
|
||||||
sortField: 'seed_keyword__volume', // Backend expects seed_keyword__volume
|
sortField: 'seed_keyword__volume', // Backend expects seed_keyword__volume
|
||||||
align: 'center' as const,
|
align: 'center' as const,
|
||||||
|
headingAlign: 'center' as const,
|
||||||
render: (value: number) => value.toLocaleString(),
|
render: (value: number) => value.toLocaleString(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...clusterColumn,
|
...clusterColumn,
|
||||||
sortable: false, // Backend doesn't support sorting by cluster_id
|
sortable: false, // Backend doesn't support sorting by cluster_id
|
||||||
sortField: 'cluster_id',
|
sortField: 'cluster_id',
|
||||||
|
width: '300px',
|
||||||
render: (_value: string, row: Keyword) => row.cluster_name ? (
|
render: (_value: string, row: Keyword) => row.cluster_name ? (
|
||||||
<Badge color="info" size="xs" variant="outline">
|
<span className="text-gray-800 dark:text-white">
|
||||||
<span className="text-[11px] font-normal">{row.cluster_name}</span>
|
{row.cluster_name}
|
||||||
</Badge>
|
</span>
|
||||||
) : <span className="text-gray-400">-</span>,
|
) : <span className="text-gray-400">-</span>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -178,6 +186,7 @@ export const createKeywordsPageConfig = (
|
|||||||
sortable: true,
|
sortable: true,
|
||||||
sortField: 'seed_keyword__difficulty', // Backend expects seed_keyword__difficulty
|
sortField: 'seed_keyword__difficulty', // Backend expects seed_keyword__difficulty
|
||||||
align: 'center' as const,
|
align: 'center' as const,
|
||||||
|
headingAlign: 'center' as const,
|
||||||
render: (value: number) => {
|
render: (value: number) => {
|
||||||
const difficultyNum = getDifficultyNumber(value);
|
const difficultyNum = getDifficultyNumber(value);
|
||||||
const difficultyBadgeColor =
|
const difficultyBadgeColor =
|
||||||
@@ -194,7 +203,7 @@ export const createKeywordsPageConfig = (
|
|||||||
: 'gray';
|
: 'gray';
|
||||||
return typeof difficultyNum === 'number' ? (
|
return typeof difficultyNum === 'number' ? (
|
||||||
<Badge color={difficultyBadgeColor} variant="soft" size="xs">
|
<Badge color={difficultyBadgeColor} variant="soft" size="xs">
|
||||||
<span className="text-[11px] font-normal">{difficultyNum}</span>
|
{difficultyNum}
|
||||||
</Badge>
|
</Badge>
|
||||||
) : (
|
) : (
|
||||||
difficultyNum
|
difficultyNum
|
||||||
@@ -206,23 +215,12 @@ export const createKeywordsPageConfig = (
|
|||||||
sortable: false, // Backend doesn't support sorting by country
|
sortable: false, // Backend doesn't support sorting by country
|
||||||
sortField: 'seed_keyword__country',
|
sortField: 'seed_keyword__country',
|
||||||
align: 'center' as const,
|
align: 'center' as const,
|
||||||
render: (value: string) => {
|
headingAlign: 'center' as const,
|
||||||
const countryNames: Record<string, string> = {
|
render: (value: string) => (
|
||||||
'US': 'United States',
|
|
||||||
'CA': 'Canada',
|
|
||||||
'GB': 'United Kingdom',
|
|
||||||
'AE': 'United Arab Emirates',
|
|
||||||
'AU': 'Australia',
|
|
||||||
'IN': 'India',
|
|
||||||
'PK': 'Pakistan',
|
|
||||||
};
|
|
||||||
const displayName = countryNames[value] || value || '-';
|
|
||||||
return (
|
|
||||||
<Badge color="info" size="xs" variant="soft">
|
<Badge color="info" size="xs" variant="soft">
|
||||||
<span className="text-[11px] font-normal">{value || '-'}</span>
|
{value || '-'}
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
),
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...statusColumn,
|
...statusColumn,
|
||||||
@@ -242,16 +240,17 @@ export const createKeywordsPageConfig = (
|
|||||||
size="xs"
|
size="xs"
|
||||||
variant="soft"
|
variant="soft"
|
||||||
>
|
>
|
||||||
<span className="text-[11px] font-normal">{properCase}</span>
|
{properCase}
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...createdColumn,
|
...createdWithActionsColumn,
|
||||||
label: 'Added',
|
label: 'Added',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
sortField: 'created_at',
|
sortField: 'created_at',
|
||||||
|
width: '130px',
|
||||||
render: (value: string) => formatRelativeDate(value),
|
render: (value: string) => formatRelativeDate(value),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -62,17 +62,18 @@ export function createReviewPageConfig(params: {
|
|||||||
label: 'Content Idea Title',
|
label: 'Content Idea Title',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
sortField: 'title',
|
sortField: 'title',
|
||||||
|
width: '400px',
|
||||||
render: (value: string, row: Content) => (
|
render: (value: string, row: Content) => (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{params.onRowClick ? (
|
{params.onRowClick ? (
|
||||||
<button
|
<button
|
||||||
onClick={() => params.onRowClick!(row)}
|
onClick={() => params.onRowClick!(row)}
|
||||||
className="text-base font-light text-brand-500 hover:text-brand-600 hover:underline text-left transition-colors"
|
className="text-sm text-brand-500 hover:text-brand-600 hover:underline text-left transition-colors"
|
||||||
>
|
>
|
||||||
{value || `Content #${row.id}`}
|
{value || `Content #${row.id}`}
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<span className="text-base font-light text-gray-900 dark:text-white">
|
<span className="text-sm text-gray-900 dark:text-white">
|
||||||
{value || `Content #${row.id}`}
|
{value || `Content #${row.id}`}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -83,7 +84,6 @@ export function createReviewPageConfig(params: {
|
|||||||
key: 'categories',
|
key: 'categories',
|
||||||
label: 'Categories',
|
label: 'Categories',
|
||||||
sortable: false,
|
sortable: false,
|
||||||
width: '180px',
|
|
||||||
render: (_value: any, row: Content) => {
|
render: (_value: any, row: Content) => {
|
||||||
const categories = row.categories || [];
|
const categories = row.categories || [];
|
||||||
if (!categories || categories.length === 0) {
|
if (!categories || categories.length === 0) {
|
||||||
@@ -107,7 +107,6 @@ export function createReviewPageConfig(params: {
|
|||||||
key: 'tags',
|
key: 'tags',
|
||||||
label: 'Tags',
|
label: 'Tags',
|
||||||
sortable: false,
|
sortable: false,
|
||||||
width: '180px',
|
|
||||||
render: (_value: any, row: Content) => {
|
render: (_value: any, row: Content) => {
|
||||||
const tags = row.tags || [];
|
const tags = row.tags || [];
|
||||||
if (!tags || tags.length === 0) {
|
if (!tags || tags.length === 0) {
|
||||||
@@ -132,7 +131,6 @@ export function createReviewPageConfig(params: {
|
|||||||
label: 'Type',
|
label: 'Type',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
sortField: 'content_type',
|
sortField: 'content_type',
|
||||||
width: '110px',
|
|
||||||
render: (value: string) => {
|
render: (value: string) => {
|
||||||
const label = TYPE_LABELS[value] || value || '-';
|
const label = TYPE_LABELS[value] || value || '-';
|
||||||
const properCase = label.charAt(0).toUpperCase() + label.slice(1);
|
const properCase = label.charAt(0).toUpperCase() + label.slice(1);
|
||||||
@@ -148,7 +146,6 @@ export function createReviewPageConfig(params: {
|
|||||||
label: 'Structure',
|
label: 'Structure',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
sortField: 'content_structure',
|
sortField: 'content_structure',
|
||||||
width: '130px',
|
|
||||||
render: (value: string) => {
|
render: (value: string) => {
|
||||||
const label = STRUCTURE_LABELS[value] || value || '-';
|
const label = STRUCTURE_LABELS[value] || value || '-';
|
||||||
const properCase = label.split(/[_\s]+/).map(word =>
|
const properCase = label.split(/[_\s]+/).map(word =>
|
||||||
@@ -165,16 +162,15 @@ export function createReviewPageConfig(params: {
|
|||||||
key: 'cluster_name',
|
key: 'cluster_name',
|
||||||
label: 'Cluster',
|
label: 'Cluster',
|
||||||
sortable: false,
|
sortable: false,
|
||||||
width: '150px',
|
|
||||||
render: (_value: any, row: Content) => {
|
render: (_value: any, row: Content) => {
|
||||||
const clusterName = row.cluster_name;
|
const clusterName = row.cluster_name;
|
||||||
if (!clusterName) {
|
if (!clusterName) {
|
||||||
return <span className="text-gray-400 dark:text-gray-500">-</span>;
|
return <span className="text-gray-400 dark:text-gray-500">-</span>;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Badge color="indigo" size="xs" variant="soft">
|
<span className="text-gray-800 dark:text-white">
|
||||||
<span className="text-[11px] font-normal">{clusterName}</span>
|
{clusterName}
|
||||||
</Badge>
|
</span>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -183,7 +179,6 @@ export function createReviewPageConfig(params: {
|
|||||||
label: 'Status',
|
label: 'Status',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
sortField: 'status',
|
sortField: 'status',
|
||||||
width: '120px',
|
|
||||||
render: (value: string, row: Content) => {
|
render: (value: string, row: Content) => {
|
||||||
const status = value || 'draft';
|
const status = value || 'draft';
|
||||||
const statusColors: Record<string, 'gray' | 'blue' | 'green' | 'amber' | 'red'> = {
|
const statusColors: Record<string, 'gray' | 'blue' | 'green' | 'amber' | 'red'> = {
|
||||||
@@ -214,7 +209,8 @@ export function createReviewPageConfig(params: {
|
|||||||
sortable: true,
|
sortable: true,
|
||||||
sortField: 'word_count',
|
sortField: 'word_count',
|
||||||
numeric: true,
|
numeric: true,
|
||||||
width: '100px',
|
align: 'center' as const,
|
||||||
|
headingAlign: 'center' as const,
|
||||||
render: (value: number) => (
|
render: (value: number) => (
|
||||||
<span className="font-mono text-sm text-gray-700 dark:text-gray-300">
|
<span className="font-mono text-sm text-gray-700 dark:text-gray-300">
|
||||||
{value?.toLocaleString() || 0}
|
{value?.toLocaleString() || 0}
|
||||||
@@ -227,7 +223,8 @@ export function createReviewPageConfig(params: {
|
|||||||
sortable: true,
|
sortable: true,
|
||||||
sortField: 'created_at',
|
sortField: 'created_at',
|
||||||
date: true,
|
date: true,
|
||||||
align: 'right',
|
width: '130px',
|
||||||
|
hasActions: true,
|
||||||
render: (value: string) => (
|
render: (value: string) => (
|
||||||
<span className="text-gray-700 dark:text-gray-300 whitespace-nowrap">
|
<span className="text-gray-700 dark:text-gray-300 whitespace-nowrap">
|
||||||
{formatRelativeDate(value)}
|
{formatRelativeDate(value)}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import React from 'react';
|
|||||||
import {
|
import {
|
||||||
titleColumn,
|
titleColumn,
|
||||||
statusColumn,
|
statusColumn,
|
||||||
createdColumn,
|
createdWithActionsColumn,
|
||||||
wordCountColumn,
|
wordCountColumn,
|
||||||
sectorColumn,
|
sectorColumn,
|
||||||
} from '../snippets/columns.snippets';
|
} from '../snippets/columns.snippets';
|
||||||
@@ -104,6 +104,7 @@ export const createTasksPageConfig = (
|
|||||||
label: 'Content Idea Title',
|
label: 'Content Idea Title',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
sortField: 'title',
|
sortField: 'title',
|
||||||
|
width: '400px',
|
||||||
toggleable: true,
|
toggleable: true,
|
||||||
toggleContentKey: 'description',
|
toggleContentKey: 'description',
|
||||||
toggleContentLabel: 'Idea & Content Outline',
|
toggleContentLabel: 'Idea & Content Outline',
|
||||||
@@ -133,8 +134,11 @@ export const createTasksPageConfig = (
|
|||||||
label: 'Cluster',
|
label: 'Cluster',
|
||||||
sortable: false, // Backend doesn't support sorting by cluster_id
|
sortable: false, // Backend doesn't support sorting by cluster_id
|
||||||
sortField: 'cluster_id',
|
sortField: 'cluster_id',
|
||||||
width: '200px',
|
render: (_value: string, row: Task) => (
|
||||||
render: (_value: string, row: Task) => row.cluster_name || '-',
|
<span className="text-gray-800 dark:text-white">
|
||||||
|
{row.cluster_name || '-'}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'taxonomy_name',
|
key: 'taxonomy_name',
|
||||||
@@ -159,7 +163,6 @@ export const createTasksPageConfig = (
|
|||||||
label: 'Type',
|
label: 'Type',
|
||||||
sortable: false, // Backend doesn't support sorting by content_type
|
sortable: false, // Backend doesn't support sorting by content_type
|
||||||
sortField: 'content_type',
|
sortField: 'content_type',
|
||||||
width: '110px',
|
|
||||||
render: (value: string) => {
|
render: (value: string) => {
|
||||||
const label = TYPE_LABELS[value] || value || '-';
|
const label = TYPE_LABELS[value] || value || '-';
|
||||||
const properCase = label.charAt(0).toUpperCase() + label.slice(1);
|
const properCase = label.charAt(0).toUpperCase() + label.slice(1);
|
||||||
@@ -175,7 +178,6 @@ export const createTasksPageConfig = (
|
|||||||
label: 'Structure',
|
label: 'Structure',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
sortField: 'content_structure',
|
sortField: 'content_structure',
|
||||||
width: '130px',
|
|
||||||
render: (value: string) => {
|
render: (value: string) => {
|
||||||
const label = STRUCTURE_LABELS[value] || value || '-';
|
const label = STRUCTURE_LABELS[value] || value || '-';
|
||||||
const properCase = label.split(/[_\s]+/).map(word =>
|
const properCase = label.split(/[_\s]+/).map(word =>
|
||||||
@@ -211,12 +213,14 @@ export const createTasksPageConfig = (
|
|||||||
sortable: true,
|
sortable: true,
|
||||||
sortField: 'word_count',
|
sortField: 'word_count',
|
||||||
align: 'center' as const,
|
align: 'center' as const,
|
||||||
|
headingAlign: 'center' as const,
|
||||||
render: (value: number | null | undefined) => (value != null ? value.toLocaleString() : '-'),
|
render: (value: number | null | undefined) => (value != null ? value.toLocaleString() : '-'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...createdColumn,
|
...createdWithActionsColumn,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
sortField: 'created_at',
|
sortField: 'created_at',
|
||||||
|
width: '130px',
|
||||||
render: (value: string) => formatRelativeDate(value),
|
render: (value: string) => formatRelativeDate(value),
|
||||||
},
|
},
|
||||||
// Optional columns - hidden by default
|
// Optional columns - hidden by default
|
||||||
|
|||||||
@@ -7,14 +7,12 @@ export const titleColumn = {
|
|||||||
key: 'title',
|
key: 'title',
|
||||||
label: 'Title',
|
label: 'Title',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
width: 'auto',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const keywordColumn = {
|
export const keywordColumn = {
|
||||||
key: 'keyword',
|
key: 'keyword',
|
||||||
label: 'Keyword',
|
label: 'Keyword',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
width: 'auto',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const statusColumn = {
|
export const statusColumn = {
|
||||||
@@ -22,7 +20,6 @@ export const statusColumn = {
|
|||||||
label: 'Status',
|
label: 'Status',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
badge: true,
|
badge: true,
|
||||||
width: '120px',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const volumeColumn = {
|
export const volumeColumn = {
|
||||||
@@ -30,7 +27,7 @@ export const volumeColumn = {
|
|||||||
label: 'Volume',
|
label: 'Volume',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
numeric: true,
|
numeric: true,
|
||||||
width: '100px',
|
align: 'center' as const,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const difficultyColumn = {
|
export const difficultyColumn = {
|
||||||
@@ -38,7 +35,7 @@ export const difficultyColumn = {
|
|||||||
label: 'Difficulty',
|
label: 'Difficulty',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
badge: true,
|
badge: true,
|
||||||
width: '120px',
|
align: 'center' as const,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const countryColumn = {
|
export const countryColumn = {
|
||||||
@@ -46,14 +43,12 @@ export const countryColumn = {
|
|||||||
label: 'Country',
|
label: 'Country',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
badge: true,
|
badge: true,
|
||||||
width: '120px',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const clusterColumn = {
|
export const clusterColumn = {
|
||||||
key: 'cluster',
|
key: 'cluster',
|
||||||
label: 'Cluster',
|
label: 'Cluster',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
width: '200px',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createdColumn = {
|
export const createdColumn = {
|
||||||
@@ -61,7 +56,16 @@ export const createdColumn = {
|
|||||||
label: 'Created',
|
label: 'Created',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
date: true,
|
date: true,
|
||||||
width: '100px',
|
};
|
||||||
|
|
||||||
|
// Combined column with date and actions in one cell
|
||||||
|
export const createdWithActionsColumn = {
|
||||||
|
key: 'created_at',
|
||||||
|
label: 'Created',
|
||||||
|
sortable: true,
|
||||||
|
date: true,
|
||||||
|
align: 'left' as const,
|
||||||
|
hasActions: true, // Special flag to indicate this column has row actions
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updatedColumn = {
|
export const updatedColumn = {
|
||||||
@@ -69,14 +73,12 @@ export const updatedColumn = {
|
|||||||
label: 'Modified',
|
label: 'Modified',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
date: true,
|
date: true,
|
||||||
width: '150px',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actionsColumn = {
|
export const actionsColumn = {
|
||||||
key: 'actions',
|
key: 'actions',
|
||||||
label: 'Actions',
|
label: 'Actions',
|
||||||
sortable: false,
|
sortable: false,
|
||||||
width: '100px',
|
|
||||||
fixed: true,
|
fixed: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -85,13 +87,12 @@ export const wordCountColumn = {
|
|||||||
label: 'Words',
|
label: 'Words',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
numeric: true,
|
numeric: true,
|
||||||
width: '120px',
|
align: 'center' as const,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sectorColumn = {
|
export const sectorColumn = {
|
||||||
key: 'sector_name',
|
key: 'sector_name',
|
||||||
label: 'Sector',
|
label: 'Sector',
|
||||||
sortable: false,
|
sortable: false,
|
||||||
width: '150px',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -663,6 +663,7 @@ export default function Keywords() {
|
|||||||
data={keywords}
|
data={keywords}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
showContent={showContent}
|
showContent={showContent}
|
||||||
|
checkboxColumnWidth="40px"
|
||||||
filters={pageConfig.filters}
|
filters={pageConfig.filters}
|
||||||
filterValues={{
|
filterValues={{
|
||||||
search: searchTerm,
|
search: searchTerm,
|
||||||
|
|||||||
@@ -20,14 +20,14 @@ export default function SetupWizard() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50 dark:bg-gray-900">
|
<div className="space-y-6">
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title="Setup Wizard"
|
title="Setup Wizard"
|
||||||
badge={{ icon: <ShootingStarIcon className="w-5 h-5" />, color: 'blue' }}
|
badge={{ icon: <ShootingStarIcon className="w-5 h-5" />, color: 'blue' }}
|
||||||
description="Complete guided setup for your site"
|
description="Complete guided setup for your site"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="max-w-4xl mx-auto py-8 px-4">
|
<div className="py-4">
|
||||||
<OnboardingWizard
|
<OnboardingWizard
|
||||||
onComplete={handleComplete}
|
onComplete={handleComplete}
|
||||||
onSkip={handleSkip}
|
onSkip={handleSkip}
|
||||||
|
|||||||
@@ -9,10 +9,14 @@
|
|||||||
these tokens. See DESIGN_SYSTEM.md for usage guidelines.
|
these tokens. See DESIGN_SYSTEM.md for usage guidelines.
|
||||||
|
|
||||||
⚠️ ONLY 6 HEX VALUES IN ENTIRE SYSTEM - Everything else derived!
|
⚠️ ONLY 6 HEX VALUES IN ENTIRE SYSTEM - Everything else derived!
|
||||||
|
⚠️ TAILWIND DEFAULT COLORS ARE DISABLED - THEY WON'T WORK!
|
||||||
|
⚠️ AI AGENTS: DO NOT USE blue-500, red-500, green-500, etc.
|
||||||
|
⚠️ USE ONLY: brand-*, success-*, warning-*, error-*, purple-*, gray-*
|
||||||
|
|
||||||
Last Updated: 2026-01-01
|
Last Updated: 2026-01-01
|
||||||
=================================================================== */
|
=================================================================== */
|
||||||
|
|
||||||
|
/* Tailwind with ALL default colors DISABLED */
|
||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
@import url("https://fonts.googleapis.com/css2?family=Outfit:wght@100..900&display=swap") layer(base);
|
@import url("https://fonts.googleapis.com/css2?family=Outfit:wght@100..900&display=swap") layer(base);
|
||||||
|
|
||||||
@@ -26,7 +30,7 @@
|
|||||||
|
|
||||||
:root {
|
:root {
|
||||||
/* ===== THE ONLY 6 HEX VALUES IN THE ENTIRE SYSTEM ===== */
|
/* ===== THE ONLY 6 HEX VALUES IN THE ENTIRE SYSTEM ===== */
|
||||||
--color-primary: #2C7AA1; /* Brand Blue - main CTA, links, primary actions */
|
--color-primary: #0077B6; /* Brand Blue - main CTA, links, primary actions */
|
||||||
--color-success: #2CA18E; /* Success Green - confirmations, positive states */
|
--color-success: #2CA18E; /* Success Green - confirmations, positive states */
|
||||||
--color-warning: #D9A12C; /* Warning Amber - alerts, cautions */
|
--color-warning: #D9A12C; /* Warning Amber - alerts, cautions */
|
||||||
--color-danger: #A12C40; /* Danger Red - errors, destructive actions */
|
--color-danger: #A12C40; /* Danger Red - errors, destructive actions */
|
||||||
@@ -82,13 +86,13 @@
|
|||||||
/* ===================================================================
|
/* ===================================================================
|
||||||
BORDER RADIUS
|
BORDER RADIUS
|
||||||
=================================================================== */
|
=================================================================== */
|
||||||
--radius-xs: 4px;
|
--radius-xs: 2px;
|
||||||
--radius-sm: 6px;
|
--radius-sm: 4px;
|
||||||
--radius-base: 8px;
|
--radius-base: 6px;
|
||||||
--radius-md: 10px;
|
--radius-md: 6px;
|
||||||
--radius-lg: 12px;
|
--radius-lg: 8px;
|
||||||
--radius-xl: 16px;
|
--radius-xl: 10px;
|
||||||
--radius-2xl: 20px;
|
--radius-2xl: 12px;
|
||||||
--radius-full: 9999px;
|
--radius-full: 9999px;
|
||||||
|
|
||||||
/* ===================================================================
|
/* ===================================================================
|
||||||
@@ -182,6 +186,18 @@
|
|||||||
--color-neutral-*: initial;
|
--color-neutral-*: initial;
|
||||||
--color-stone-*: initial;
|
--color-stone-*: initial;
|
||||||
|
|
||||||
|
/* -----------------------------------------------------------------
|
||||||
|
BORDER RADIUS (Override Tailwind defaults)
|
||||||
|
----------------------------------------------------------------- */
|
||||||
|
--radius-xs: 2px;
|
||||||
|
--radius-sm: 4px;
|
||||||
|
--radius-md: 6px;
|
||||||
|
--radius-lg: 8px;
|
||||||
|
--radius-xl: 10px;
|
||||||
|
--radius-2xl: 12px;
|
||||||
|
--radius-3xl: 16px;
|
||||||
|
--radius-4xl: 20px;
|
||||||
|
|
||||||
/* -----------------------------------------------------------------
|
/* -----------------------------------------------------------------
|
||||||
FONTS
|
FONTS
|
||||||
----------------------------------------------------------------- */
|
----------------------------------------------------------------- */
|
||||||
@@ -481,18 +497,23 @@
|
|||||||
SECTION 7: TABLE STYLES
|
SECTION 7: TABLE STYLES
|
||||||
=================================================================== */
|
=================================================================== */
|
||||||
|
|
||||||
|
/* Global Table Font Size */
|
||||||
|
.igny8-table-compact th,
|
||||||
|
.igny8-table-compact td {
|
||||||
|
font-size: 13px !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* Compact Table Headers */
|
/* Compact Table Headers */
|
||||||
.igny8-table-compact th {
|
.igny8-table-compact th {
|
||||||
padding: 8px 12px !important;
|
padding: 8px 12px !important;
|
||||||
font-size: 13px !important;
|
font-weight: 500 !important;
|
||||||
font-weight: 600 !important;
|
|
||||||
color: var(--color-text-dim) !important;
|
color: var(--color-text-dim) !important;
|
||||||
text-align: left !important;
|
text-align: left !important;
|
||||||
background-color: var(--color-surface) !important;
|
background-color: var(--color-surface) !important;
|
||||||
border-bottom: 2px solid var(--color-stroke) !important;
|
border-bottom: 1px solid var(--color-stroke) !important;
|
||||||
text-transform: capitalize;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.3px;
|
letter-spacing: 0.5px;
|
||||||
line-height: 1.3 !important;
|
line-height: 1.4 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .igny8-table-compact th {
|
.dark .igny8-table-compact th {
|
||||||
@@ -502,9 +523,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.igny8-table-compact td {
|
.igny8-table-compact td {
|
||||||
padding: 6px 10px !important;
|
padding: 8px !important;
|
||||||
font-size: 13px !important;
|
line-height: 1.4 !important;
|
||||||
line-height: 1.3 !important;
|
|
||||||
border-bottom: 1px solid var(--color-stroke) !important;
|
border-bottom: 1px solid var(--color-stroke) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -793,7 +793,7 @@ export default function ContentViewTemplate({ content, loading, onBack }: Conten
|
|||||||
return 'bg-success-100 text-success-800 dark:bg-success-900/30 dark:text-success-400';
|
return 'bg-success-100 text-success-800 dark:bg-success-900/30 dark:text-success-400';
|
||||||
}
|
}
|
||||||
if (statusLower === 'approved') {
|
if (statusLower === 'approved') {
|
||||||
return 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400';
|
return 'bg-brand-100 text-brand-800 dark:bg-brand-900/30 dark:text-brand-400';
|
||||||
}
|
}
|
||||||
if (statusLower === 'review') {
|
if (statusLower === 'review') {
|
||||||
return 'bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-400';
|
return 'bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-400';
|
||||||
|
|||||||
@@ -81,11 +81,13 @@ interface ColumnConfig {
|
|||||||
width?: string;
|
width?: string;
|
||||||
fixed?: boolean;
|
fixed?: boolean;
|
||||||
align?: 'left' | 'center' | 'right';
|
align?: 'left' | 'center' | 'right';
|
||||||
|
headingAlign?: 'left' | 'center' | 'right'; // Separate alignment for heading
|
||||||
render?: (value: any, row: any) => ReactNode;
|
render?: (value: any, row: any) => ReactNode;
|
||||||
toggleable?: boolean; // If true, this column will have a toggle button for expanding content
|
toggleable?: boolean; // If true, this column will have a toggle button for expanding content
|
||||||
toggleContentKey?: string; // Key of the field containing content to display when toggled
|
toggleContentKey?: string; // Key of the field containing content to display when toggled
|
||||||
toggleContentLabel?: string; // Label for the expanded content (e.g., "Content Outline")
|
toggleContentLabel?: string; // Label for the expanded content (e.g., "Content Outline")
|
||||||
defaultVisible?: boolean; // Whether column is visible by default (default: true)
|
defaultVisible?: boolean; // Whether column is visible by default (default: true)
|
||||||
|
hasActions?: boolean; // If true, this column will include row actions (3-dot menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FilterConfig {
|
interface FilterConfig {
|
||||||
@@ -179,6 +181,8 @@ interface TablePageTemplateProps {
|
|||||||
};
|
};
|
||||||
// Custom row highlight function (returns bg class based on row data)
|
// Custom row highlight function (returns bg class based on row data)
|
||||||
getRowClassName?: (row: any) => string;
|
getRowClassName?: (row: any) => string;
|
||||||
|
// Custom checkbox column width (default: w-12 = 48px)
|
||||||
|
checkboxColumnWidth?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function TablePageTemplate({
|
export default function TablePageTemplate({
|
||||||
@@ -217,6 +221,7 @@ export default function TablePageTemplate({
|
|||||||
bulkActions: customBulkActions,
|
bulkActions: customBulkActions,
|
||||||
primaryAction,
|
primaryAction,
|
||||||
getRowClassName,
|
getRowClassName,
|
||||||
|
checkboxColumnWidth = '48px',
|
||||||
}: TablePageTemplateProps) {
|
}: TablePageTemplateProps) {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const [isBulkActionsDropdownOpen, setIsBulkActionsDropdownOpen] = useState(false);
|
const [isBulkActionsDropdownOpen, setIsBulkActionsDropdownOpen] = useState(false);
|
||||||
@@ -799,7 +804,8 @@ export default function TablePageTemplate({
|
|||||||
{selection && (
|
{selection && (
|
||||||
<TableCell
|
<TableCell
|
||||||
isHeader
|
isHeader
|
||||||
className="font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400 w-12"
|
className="font-medium text-gray-500 text-center text-theme-xs dark:text-gray-400"
|
||||||
|
style={{ width: checkboxColumnWidth, maxWidth: checkboxColumnWidth }}
|
||||||
>
|
>
|
||||||
{showContent && (
|
{showContent && (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
@@ -816,15 +822,19 @@ export default function TablePageTemplate({
|
|||||||
)}
|
)}
|
||||||
{visibleColumnsList.map((column, colIndex) => {
|
{visibleColumnsList.map((column, colIndex) => {
|
||||||
const isLastColumn = colIndex === visibleColumnsList.length - 1;
|
const isLastColumn = colIndex === visibleColumnsList.length - 1;
|
||||||
|
const hasActionsInColumn = column.hasActions && rowActions.length > 0;
|
||||||
const displayName = column.label || formatColumnKey(column.key);
|
const displayName = column.label || formatColumnKey(column.key);
|
||||||
|
const headerAlign = column.headingAlign || column.align || 'start';
|
||||||
|
const justifyClass = headerAlign === 'center' ? 'justify-center' : headerAlign === 'right' ? 'justify-end' : 'justify-start';
|
||||||
return (
|
return (
|
||||||
<TableCell
|
<TableCell
|
||||||
key={column.key}
|
key={column.key}
|
||||||
isHeader
|
isHeader
|
||||||
className={`font-medium text-gray-500 text-${column.align || 'start'} text-[11px] dark:text-gray-400 ${column.sortable ? 'cursor-pointer hover:text-gray-700 dark:hover:text-gray-300' : ''} ${isLastColumn && rowActions.length > 0 ? 'pr-16' : ''}`}
|
className={`font-medium text-gray-500 text-${hasActionsInColumn ? 'start' : headerAlign} dark:text-gray-400 ${column.sortable ? 'cursor-pointer hover:text-gray-700 dark:hover:text-gray-300' : ''} ${hasActionsInColumn ? 'pr-16' : ''}`}
|
||||||
|
style={column.width ? { width: column.width } : undefined}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className={`flex items-center gap-2 ${hasActionsInColumn ? 'justify-between' : justifyClass}`}>
|
||||||
<div className="flex items-center flex-1">
|
<div className={`flex items-center ${hasActionsInColumn ? 'flex-1' : justifyClass}`}>
|
||||||
{column.sortable ? (
|
{column.sortable ? (
|
||||||
<div onClick={() => handleSort(column)} className="flex items-center">
|
<div onClick={() => handleSort(column)} className="flex items-center">
|
||||||
{displayName}
|
{displayName}
|
||||||
@@ -915,7 +925,10 @@ export default function TablePageTemplate({
|
|||||||
className={`igny8-data-row ${isRowAdded ? 'bg-brand-50 dark:bg-brand-500/10' : ''} ${customRowClass}`}
|
className={`igny8-data-row ${isRowAdded ? 'bg-brand-50 dark:bg-brand-500/10' : ''} ${customRowClass}`}
|
||||||
>
|
>
|
||||||
{selection && (
|
{selection && (
|
||||||
<TableCell className="text-start">
|
<TableCell
|
||||||
|
className="text-center"
|
||||||
|
style={{ width: checkboxColumnWidth, maxWidth: checkboxColumnWidth }}
|
||||||
|
>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={selectedIds.includes(row.id?.toString() || '')}
|
checked={selectedIds.includes(row.id?.toString() || '')}
|
||||||
onChange={(checked) => handleSelectRow(row.id?.toString() || '', checked)}
|
onChange={(checked) => handleSelectRow(row.id?.toString() || '', checked)}
|
||||||
@@ -925,11 +938,11 @@ export default function TablePageTemplate({
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
)}
|
)}
|
||||||
{visibleColumnsList.map((column, colIndex) => {
|
{visibleColumnsList.map((column, colIndex) => {
|
||||||
const isLastColumn = colIndex === visibleColumnsList.length - 1;
|
|
||||||
const rowId = row.id || index;
|
const rowId = row.id || index;
|
||||||
|
const hasActionsInColumn = column.hasActions && rowActions.length > 0;
|
||||||
|
|
||||||
// Get or create ref for this row's actions button
|
// Get or create ref for this row's actions button
|
||||||
if (isLastColumn && rowActions.length > 0) {
|
if (hasActionsInColumn) {
|
||||||
if (!rowActionButtonRefs.current.has(rowId)) {
|
if (!rowActionButtonRefs.current.has(rowId)) {
|
||||||
const ref = React.createRef<HTMLButtonElement>();
|
const ref = React.createRef<HTMLButtonElement>();
|
||||||
rowActionButtonRefs.current.set(rowId, ref);
|
rowActionButtonRefs.current.set(rowId, ref);
|
||||||
@@ -939,7 +952,7 @@ export default function TablePageTemplate({
|
|||||||
return (
|
return (
|
||||||
<TableCell
|
<TableCell
|
||||||
key={column.key}
|
key={column.key}
|
||||||
className={`text-${column.align || 'start'} text-gray-800 dark:text-white/90 ${isLastColumn && rowActions.length > 0 ? 'relative pr-16' : ''}`}
|
className={`text-${column.align || 'start'} text-gray-800 dark:text-white/90 ${hasActionsInColumn ? 'relative pr-16' : ''}`}
|
||||||
>
|
>
|
||||||
<div className={`flex items-center ${column.align === 'center' ? 'justify-center' : column.align === 'end' ? 'justify-end' : ''} ${column.toggleable && hasToggleContent ? 'justify-between w-full' : ''} gap-2`}>
|
<div className={`flex items-center ${column.align === 'center' ? 'justify-center' : column.align === 'end' ? 'justify-end' : ''} ${column.toggleable && hasToggleContent ? 'justify-between w-full' : ''} gap-2`}>
|
||||||
<div className={column.toggleable && hasToggleContent ? 'flex-1' : ''}>
|
<div className={column.toggleable && hasToggleContent ? 'flex-1' : ''}>
|
||||||
@@ -962,8 +975,8 @@ export default function TablePageTemplate({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Actions button - absolutely positioned in last column */}
|
{/* Actions button - absolutely positioned in column with hasActions flag */}
|
||||||
{isLastColumn && rowActions.length > 0 && (() => {
|
{hasActionsInColumn && (() => {
|
||||||
// Check if row is already added - use same logic as handleBulkAddSelected
|
// Check if row is already added - use same logic as handleBulkAddSelected
|
||||||
const isRowAdded = !!(row as any).isAdded;
|
const isRowAdded = !!(row as any).isAdded;
|
||||||
|
|
||||||
@@ -1172,7 +1185,7 @@ export default function TablePageTemplate({
|
|||||||
|
|
||||||
{/* Pagination - Compact icon-based pagination */}
|
{/* Pagination - Compact icon-based pagination */}
|
||||||
{pagination && (
|
{pagination && (
|
||||||
<div className="mt-6 border-t border-gray-200 dark:border-gray-800 pt-6">
|
<div className="mt-4 bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 px-4 py-3">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="text-sm text-gray-500 dark:text-gray-400">
|
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||||||
Showing {data.length} of {pagination.totalCount} {selectionLabel || 'items'}
|
Showing {data.length} of {pagination.totalCount} {selectionLabel || 'items'}
|
||||||
|
|||||||
Reference in New Issue
Block a user