feat: implement Project Management with Gantt Chart, Milestones, and CRM enhancements
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import type { CollectionConfig } from "payload";
|
||||
import { aiEndpointHandler } from "../endpoints/aiEndpoint";
|
||||
|
||||
export const CrmAccounts: CollectionConfig = {
|
||||
slug: "crm-accounts",
|
||||
@@ -10,7 +11,16 @@ export const CrmAccounts: CollectionConfig = {
|
||||
useAsTitle: "name",
|
||||
defaultColumns: ["name", "status", "leadTemperature", "updatedAt"],
|
||||
group: "CRM",
|
||||
description:
|
||||
"Accounts represent companies or organizations. They are the central hub linking Contacts and Interactions together. Use this to track the overall relationship status.",
|
||||
},
|
||||
endpoints: [
|
||||
{
|
||||
path: "/:id/analyze",
|
||||
method: "post",
|
||||
handler: aiEndpointHandler,
|
||||
},
|
||||
],
|
||||
access: {
|
||||
read: ({ req: { user } }) => Boolean(user), // Admin only
|
||||
create: ({ req: { user } }) => Boolean(user),
|
||||
@@ -23,7 +33,7 @@ export const CrmAccounts: CollectionConfig = {
|
||||
type: "ui",
|
||||
admin: {
|
||||
components: {
|
||||
Field: "/src/payload/components/AiAnalyzeButton#AiAnalyzeButton",
|
||||
Field: "@/src/payload/components/AiAnalyzeButton#AiAnalyzeButton",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -31,14 +41,20 @@ export const CrmAccounts: CollectionConfig = {
|
||||
name: "name",
|
||||
type: "text",
|
||||
required: true,
|
||||
label: "Company / Account Name",
|
||||
label: "Company / Project Name",
|
||||
admin: {
|
||||
description:
|
||||
"Enter the official name of the business or the research project name.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "website",
|
||||
type: "text",
|
||||
label: "Website URL",
|
||||
admin: {
|
||||
description: "The website of the account, useful for AI analysis.",
|
||||
description:
|
||||
"The main website of the account. Required for triggering the AI Website Analysis.",
|
||||
placeholder: "https://example.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -48,29 +64,31 @@ export const CrmAccounts: CollectionConfig = {
|
||||
name: "status",
|
||||
type: "select",
|
||||
options: [
|
||||
{ label: "Lead", value: "lead" },
|
||||
{ label: "Client", value: "client" },
|
||||
{ label: "Lost", value: "lost" },
|
||||
{ label: "Lead (Prospect)", value: "lead" },
|
||||
{ label: "Active Client", value: "client" },
|
||||
{ label: "Business Partner", value: "partner" },
|
||||
{ label: "Lost / Archive", value: "lost" },
|
||||
],
|
||||
defaultValue: "lead",
|
||||
admin: {
|
||||
width: "50%",
|
||||
description: "Change from Lead to Client upon conversion.",
|
||||
description: "Current lifecycle stage of this business relation.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "leadTemperature",
|
||||
type: "select",
|
||||
options: [
|
||||
{ label: "Cold", value: "cold" },
|
||||
{ label: "Warm", value: "warm" },
|
||||
{ label: "Hot", value: "hot" },
|
||||
{ label: "❄️ Cold (New Research)", value: "cold" },
|
||||
{ label: "🔥 Warm (In Contact)", value: "warm" },
|
||||
{ label: "⚡ Hot (Negotiation / Quote)", value: "hot" },
|
||||
],
|
||||
admin: {
|
||||
condition: (data) => {
|
||||
return data?.status === "lead";
|
||||
},
|
||||
width: "50%",
|
||||
description: "Indicates how likely this lead is to convert soon.",
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -79,7 +97,10 @@ export const CrmAccounts: CollectionConfig = {
|
||||
name: "assignedTo",
|
||||
type: "relationship",
|
||||
relationTo: "users",
|
||||
label: "Assigned To (User)",
|
||||
label: "Account Manager (User)",
|
||||
admin: {
|
||||
description: "The internal team member responsible for this account.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "reports",
|
||||
@@ -89,7 +110,45 @@ export const CrmAccounts: CollectionConfig = {
|
||||
label: "AI Reports & Documents",
|
||||
admin: {
|
||||
description:
|
||||
"PDFs and strategy documents generated by AI or attached manually.",
|
||||
"All generated PDF estimates and strategy documents appear here.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "topics",
|
||||
type: "join",
|
||||
collection: "crm-topics",
|
||||
on: "account",
|
||||
admin: {
|
||||
description:
|
||||
"Projects, deals, or specific topics active for this client.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "contacts",
|
||||
type: "join",
|
||||
collection: "crm-contacts",
|
||||
on: "account",
|
||||
admin: {
|
||||
description: "All contacts associated with this account.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "interactions",
|
||||
type: "join",
|
||||
collection: "crm-interactions",
|
||||
on: "account",
|
||||
admin: {
|
||||
description:
|
||||
"Timeline of all communication logged against this account.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "projects",
|
||||
type: "join",
|
||||
collection: "projects",
|
||||
on: "account",
|
||||
admin: {
|
||||
description: "All high-level projects associated with this account.",
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -7,9 +7,11 @@ export const CrmContacts: CollectionConfig = {
|
||||
plural: "Contacts",
|
||||
},
|
||||
admin: {
|
||||
useAsTitle: "email", // Fallback, will define an afterRead hook or virtual field for a better title
|
||||
defaultColumns: ["firstName", "lastName", "email", "account"],
|
||||
useAsTitle: "fullName",
|
||||
defaultColumns: ["fullName", "email", "account"],
|
||||
group: "CRM",
|
||||
description:
|
||||
"Contacts are the individual people linked to an Account. A person should only be created once and can be assigned to a company here.",
|
||||
},
|
||||
access: {
|
||||
read: ({ req: { user } }) => Boolean(user),
|
||||
@@ -17,7 +19,36 @@ export const CrmContacts: CollectionConfig = {
|
||||
update: ({ req: { user } }) => Boolean(user),
|
||||
delete: ({ req: { user } }) => Boolean(user),
|
||||
},
|
||||
hooks: {
|
||||
beforeChange: [
|
||||
({ data }) => {
|
||||
if (data?.firstName || data?.lastName) {
|
||||
data.fullName =
|
||||
`${data.firstName || ""} ${data.lastName || ""}`.trim();
|
||||
}
|
||||
return data;
|
||||
},
|
||||
],
|
||||
afterRead: [
|
||||
({ doc }) => {
|
||||
if (!doc.fullName && (doc.firstName || doc.lastName)) {
|
||||
return {
|
||||
...doc,
|
||||
fullName: `${doc.firstName || ""} ${doc.lastName || ""}`.trim(),
|
||||
};
|
||||
}
|
||||
return doc;
|
||||
},
|
||||
],
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: "fullName",
|
||||
type: "text",
|
||||
admin: {
|
||||
hidden: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "row",
|
||||
fields: [
|
||||
@@ -44,6 +75,9 @@ export const CrmContacts: CollectionConfig = {
|
||||
type: "email",
|
||||
required: true,
|
||||
unique: true,
|
||||
admin: {
|
||||
description: "Primary email address for communication tracking.",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "row",
|
||||
@@ -60,6 +94,7 @@ export const CrmContacts: CollectionConfig = {
|
||||
type: "text",
|
||||
admin: {
|
||||
width: "50%",
|
||||
placeholder: "https://linkedin.com/in/...",
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -68,12 +103,29 @@ export const CrmContacts: CollectionConfig = {
|
||||
name: "role",
|
||||
type: "text",
|
||||
label: "Job Title / Role",
|
||||
admin: {
|
||||
description: "e.g. CEO, Marketing Manager, Technical Lead",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "account",
|
||||
type: "relationship",
|
||||
relationTo: "crm-accounts",
|
||||
label: "Company / Account",
|
||||
admin: {
|
||||
description:
|
||||
"Link this person to an organization from the Accounts collection.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "interactions",
|
||||
type: "join",
|
||||
collection: "crm-interactions",
|
||||
on: "contact",
|
||||
admin: {
|
||||
description:
|
||||
"Timeline of all communication logged directly with this person.",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import type { CollectionConfig } from "payload";
|
||||
import { sendEmailOnOutboundInteraction } from "../hooks/sendEmailOnOutboundInteraction";
|
||||
import { lexicalEditor } from "@payloadcms/richtext-lexical";
|
||||
|
||||
export const CrmInteractions: CollectionConfig = {
|
||||
slug: "crm-interactions",
|
||||
labels: {
|
||||
singular: "Interaction",
|
||||
plural: "Interactions",
|
||||
singular: "Journal Entry",
|
||||
plural: "Journal",
|
||||
},
|
||||
admin: {
|
||||
useAsTitle: "subject",
|
||||
defaultColumns: ["type", "direction", "subject", "date", "contact"],
|
||||
defaultColumns: ["type", "subject", "date", "contact", "account"],
|
||||
group: "CRM",
|
||||
description:
|
||||
"Your CRM journal. Log what happened, when, on which channel, and attach any relevant files. This is for summaries and facts — not for sending messages.",
|
||||
},
|
||||
access: {
|
||||
read: ({ req: { user } }) => Boolean(user),
|
||||
@@ -18,9 +20,6 @@ export const CrmInteractions: CollectionConfig = {
|
||||
update: ({ req: { user } }) => Boolean(user),
|
||||
delete: ({ req: { user } }) => Boolean(user),
|
||||
},
|
||||
hooks: {
|
||||
afterChange: [sendEmailOnOutboundInteraction],
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
type: "row",
|
||||
@@ -28,25 +27,76 @@ export const CrmInteractions: CollectionConfig = {
|
||||
{
|
||||
name: "type",
|
||||
type: "select",
|
||||
label: "Channel",
|
||||
options: [
|
||||
{ label: "Email", value: "email" },
|
||||
{ label: "Call", value: "call" },
|
||||
{ label: "Meeting", value: "meeting" },
|
||||
{ label: "Note", value: "note" },
|
||||
{ label: "📧 Email", value: "email" },
|
||||
{ label: "📞 Phone Call", value: "call" },
|
||||
{ label: "🤝 Meeting", value: "meeting" },
|
||||
{ label: "📱 WhatsApp", value: "whatsapp" },
|
||||
{ label: "🌐 Social Media", value: "social" },
|
||||
{ label: "📄 Document / File", value: "document" },
|
||||
{ label: "📝 Internal Note", value: "note" },
|
||||
],
|
||||
required: true,
|
||||
defaultValue: "email",
|
||||
defaultValue: "note",
|
||||
admin: {
|
||||
width: "50%",
|
||||
description: "Where did this communication take place?",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "direction",
|
||||
type: "select",
|
||||
options: [
|
||||
{ label: "Inbound", value: "inbound" },
|
||||
{ label: "Outbound", value: "outbound" },
|
||||
{ label: "📥 Incoming (from Client)", value: "inbound" },
|
||||
{ label: "📤 Outgoing (to Client)", value: "outbound" },
|
||||
],
|
||||
admin: {
|
||||
hidden: true, // Hide from UI to prevent usage, but keep in DB schema to avoid Drizzle prompts
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "date",
|
||||
type: "date",
|
||||
required: true,
|
||||
defaultValue: () => new Date().toISOString(),
|
||||
admin: {
|
||||
width: "50%",
|
||||
date: {
|
||||
pickerAppearance: "dayAndTime",
|
||||
},
|
||||
description: "When did this happen?",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "subject",
|
||||
type: "text",
|
||||
required: true,
|
||||
label: "Subject / Title",
|
||||
admin: {
|
||||
placeholder: "e.g. Herr X hat Website-Relaunch beauftragt",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "row",
|
||||
fields: [
|
||||
{
|
||||
name: "contact",
|
||||
type: "relationship",
|
||||
relationTo: "crm-contacts",
|
||||
label: "Contact Person",
|
||||
admin: {
|
||||
width: "50%",
|
||||
description: "Who was involved?",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "account",
|
||||
type: "relationship",
|
||||
relationTo: "crm-accounts",
|
||||
label: "Company / Account",
|
||||
admin: {
|
||||
width: "50%",
|
||||
},
|
||||
@@ -54,37 +104,40 @@ export const CrmInteractions: CollectionConfig = {
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "date",
|
||||
type: "date",
|
||||
required: true,
|
||||
defaultValue: () => new Date().toISOString(),
|
||||
name: "topic",
|
||||
type: "relationship",
|
||||
relationTo: "crm-topics",
|
||||
label: "Related Topic",
|
||||
admin: {
|
||||
date: {
|
||||
pickerAppearance: "dayAndTime",
|
||||
description:
|
||||
"Optional: Group this entry under a specific project or topic.",
|
||||
condition: (data) => {
|
||||
return Boolean(data?.account);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "contact",
|
||||
type: "relationship",
|
||||
relationTo: "crm-contacts",
|
||||
label: "Contact Person",
|
||||
},
|
||||
{
|
||||
name: "account",
|
||||
type: "relationship",
|
||||
relationTo: "crm-accounts",
|
||||
label: "Account / Company",
|
||||
},
|
||||
{
|
||||
name: "subject",
|
||||
type: "text",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "content",
|
||||
type: "richText",
|
||||
label: "Content / Notes / Email Body",
|
||||
label: "Summary / Notes",
|
||||
editor: lexicalEditor({
|
||||
features: ({ defaultFeatures }) => [...defaultFeatures],
|
||||
}),
|
||||
admin: {
|
||||
description:
|
||||
"Summarize what happened, what was decided, or what the next steps are.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "attachments",
|
||||
type: "relationship",
|
||||
relationTo: "media",
|
||||
hasMany: true,
|
||||
label: "Attachments",
|
||||
admin: {
|
||||
description:
|
||||
"Attach received documents, screenshots, contracts, or any relevant files.",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
78
apps/web/src/payload/collections/CrmTopics.ts
Normal file
78
apps/web/src/payload/collections/CrmTopics.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import type { CollectionConfig } from "payload";
|
||||
|
||||
export const CrmTopics: CollectionConfig = {
|
||||
slug: "crm-topics",
|
||||
labels: {
|
||||
singular: "Topic",
|
||||
plural: "Topics",
|
||||
},
|
||||
admin: {
|
||||
useAsTitle: "title",
|
||||
defaultColumns: ["title", "account", "status"],
|
||||
group: "CRM",
|
||||
description:
|
||||
"Group your interactions (emails, calls, notes) into Topics. This helps you keep track of specific projects with a client.",
|
||||
},
|
||||
access: {
|
||||
read: ({ req: { user } }) => Boolean(user),
|
||||
create: ({ req: { user } }) => Boolean(user),
|
||||
update: ({ req: { user } }) => Boolean(user),
|
||||
delete: ({ req: { user } }) => Boolean(user),
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: "title",
|
||||
type: "text",
|
||||
required: true,
|
||||
label: "Topic Name",
|
||||
admin: {
|
||||
placeholder: "e.g. Website Relaunch 2026",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "account",
|
||||
type: "relationship",
|
||||
relationTo: "crm-accounts",
|
||||
required: true,
|
||||
label: "Client / Account",
|
||||
admin: {
|
||||
description: "Which account does this topic belong to?",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "status",
|
||||
type: "select",
|
||||
options: [
|
||||
{ label: "🟢 Active / Open", value: "active" },
|
||||
{ label: "🟡 On Hold", value: "paused" },
|
||||
{ label: "🔴 Closed / Won", value: "won" },
|
||||
{ label: "⚫ Closed / Lost", value: "lost" },
|
||||
],
|
||||
defaultValue: "active",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "stage",
|
||||
type: "select",
|
||||
options: [
|
||||
{ label: "Discovery / Briefing", value: "discovery" },
|
||||
{ label: "Proposal / Quote sent", value: "proposal" },
|
||||
{ label: "Negotiation", value: "negotiation" },
|
||||
{ label: "Implementation", value: "implementation" },
|
||||
],
|
||||
admin: {
|
||||
description: "Optional: What stage is this deal/project currently in?",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "interactions",
|
||||
type: "join",
|
||||
collection: "crm-interactions",
|
||||
on: "topic",
|
||||
admin: {
|
||||
description:
|
||||
"Timeline of all emails and notes specifically related to this topic.",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { CollectionConfig } from "payload";
|
||||
import { convertInquiryEndpoint } from "../endpoints/convertInquiryEndpoint";
|
||||
|
||||
export const Inquiries: CollectionConfig = {
|
||||
slug: "inquiries",
|
||||
@@ -17,7 +18,36 @@ export const Inquiries: CollectionConfig = {
|
||||
update: ({ req: { user } }) => Boolean(user),
|
||||
delete: ({ req: { user } }) => Boolean(user),
|
||||
},
|
||||
endpoints: [
|
||||
{
|
||||
path: "/:id/convert-to-lead",
|
||||
method: "post",
|
||||
handler: convertInquiryEndpoint,
|
||||
},
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
name: "convertButton",
|
||||
type: "ui",
|
||||
admin: {
|
||||
components: {
|
||||
Field:
|
||||
"@/src/payload/components/ConvertInquiryButton#ConvertInquiryButton",
|
||||
},
|
||||
condition: (data) => {
|
||||
return !data?.processed;
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "processed",
|
||||
type: "checkbox",
|
||||
defaultValue: false,
|
||||
admin: {
|
||||
description: "Has this inquiry been converted into a CRM Lead?",
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "name",
|
||||
type: "text",
|
||||
|
||||
192
apps/web/src/payload/collections/Projects.ts
Normal file
192
apps/web/src/payload/collections/Projects.ts
Normal file
@@ -0,0 +1,192 @@
|
||||
import type { CollectionConfig } from "payload";
|
||||
|
||||
export const Projects: CollectionConfig = {
|
||||
slug: "projects",
|
||||
labels: {
|
||||
singular: "Project",
|
||||
plural: "Projects",
|
||||
},
|
||||
admin: {
|
||||
useAsTitle: "title",
|
||||
defaultColumns: ["title", "account", "status", "startDate", "targetDate"],
|
||||
group: "Project Management",
|
||||
description: "Manage high-level projects for your clients.",
|
||||
components: {
|
||||
beforeListTable: ["@/src/payload/views/GanttChart#GanttChartView"],
|
||||
},
|
||||
},
|
||||
access: {
|
||||
read: ({ req: { user } }) => Boolean(user),
|
||||
create: ({ req: { user } }) => Boolean(user),
|
||||
update: ({ req: { user } }) => Boolean(user),
|
||||
delete: ({ req: { user } }) => Boolean(user),
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: "title",
|
||||
type: "text",
|
||||
required: true,
|
||||
label: "Project Title",
|
||||
},
|
||||
{
|
||||
name: "account",
|
||||
type: "relationship",
|
||||
relationTo: "crm-accounts",
|
||||
required: true,
|
||||
label: "Client / Account",
|
||||
admin: {
|
||||
description: "Which account is this project for?",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "contact",
|
||||
type: "relationship",
|
||||
relationTo: "crm-contacts",
|
||||
hasMany: true,
|
||||
label: "Project Stakeholders",
|
||||
admin: {
|
||||
description:
|
||||
"Key contacts from the client side involved in this project.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "status",
|
||||
type: "select",
|
||||
options: [
|
||||
{ label: "Draft", value: "draft" },
|
||||
{ label: "In Progress", value: "in_progress" },
|
||||
{ label: "Review", value: "review" },
|
||||
{ label: "Completed", value: "completed" },
|
||||
],
|
||||
defaultValue: "draft",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: "row",
|
||||
fields: [
|
||||
{
|
||||
name: "startDate",
|
||||
type: "date",
|
||||
label: "Start Date",
|
||||
admin: { width: "25%" },
|
||||
},
|
||||
{
|
||||
name: "targetDate",
|
||||
type: "date",
|
||||
label: "Target Date",
|
||||
admin: { width: "25%" },
|
||||
},
|
||||
{
|
||||
name: "valueMin",
|
||||
type: "number",
|
||||
label: "Value From (€)",
|
||||
admin: {
|
||||
width: "25%",
|
||||
placeholder: "z.B. 5000",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valueMax",
|
||||
type: "number",
|
||||
label: "Value To (€)",
|
||||
admin: {
|
||||
width: "25%",
|
||||
placeholder: "z.B. 8000",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "briefing",
|
||||
type: "richText",
|
||||
label: "Briefing",
|
||||
admin: {
|
||||
description: "Project briefing, requirements, or notes.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "attachments",
|
||||
type: "upload",
|
||||
relationTo: "media",
|
||||
hasMany: true,
|
||||
label: "Attachments",
|
||||
admin: {
|
||||
description:
|
||||
"Upload files, documents, or assets related to this project.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "milestones",
|
||||
type: "array",
|
||||
label: "Milestones",
|
||||
admin: {
|
||||
description: "Granular deliverables or milestones within this project.",
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: "name",
|
||||
type: "text",
|
||||
required: true,
|
||||
label: "Milestone Name",
|
||||
admin: {
|
||||
placeholder: "e.g. Authentication System",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "row",
|
||||
fields: [
|
||||
{
|
||||
name: "status",
|
||||
type: "select",
|
||||
options: [
|
||||
{ label: "To Do", value: "todo" },
|
||||
{ label: "In Progress", value: "in_progress" },
|
||||
{ label: "Done", value: "done" },
|
||||
],
|
||||
defaultValue: "todo",
|
||||
required: true,
|
||||
admin: { width: "50%" },
|
||||
},
|
||||
{
|
||||
name: "priority",
|
||||
type: "select",
|
||||
options: [
|
||||
{ label: "Low", value: "low" },
|
||||
{ label: "Medium", value: "medium" },
|
||||
{ label: "High", value: "high" },
|
||||
],
|
||||
defaultValue: "medium",
|
||||
admin: { width: "50%" },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "row",
|
||||
fields: [
|
||||
{
|
||||
name: "startDate",
|
||||
type: "date",
|
||||
label: "Start Date",
|
||||
admin: { width: "50%" },
|
||||
},
|
||||
{
|
||||
name: "targetDate",
|
||||
type: "date",
|
||||
label: "Target Date",
|
||||
admin: { width: "50%" },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "assignee",
|
||||
type: "relationship",
|
||||
relationTo: "users",
|
||||
label: "Assignee",
|
||||
admin: {
|
||||
description: "Internal team member responsible for this milestone.",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
Reference in New Issue
Block a user