feat(vtiger-crm): add create todo action
This commit is contained in:
@@ -0,0 +1,357 @@
|
|||||||
|
export const fields = [
|
||||||
|
{
|
||||||
|
label: 'Name',
|
||||||
|
key: 'name',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Assigned To',
|
||||||
|
key: 'assignedTo',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
description: 'Default is the id of the account connected to Automatisch.',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Start Date & Time',
|
||||||
|
key: 'startDateAndTime',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
description: 'Format: yyyy-mm-dd',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Due Date',
|
||||||
|
key: 'dueDate',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
description: 'Format: yyyy-mm-dd',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Stage',
|
||||||
|
key: 'stage',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listTodoOptions',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.stage',
|
||||||
|
value: 'taskstatus',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Contact Name',
|
||||||
|
key: 'contactName',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listContacts',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Priority',
|
||||||
|
key: 'priority',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
options: [
|
||||||
|
{ label: 'High', value: 'High' },
|
||||||
|
{ label: 'Medium', value: 'Medium' },
|
||||||
|
{ label: 'Low', value: 'Low' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Send Notification',
|
||||||
|
key: 'sendNotification',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
options: [
|
||||||
|
{ label: 'True', value: 'true' },
|
||||||
|
{ label: 'False', value: 'false' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Location',
|
||||||
|
key: 'location',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Record Currency',
|
||||||
|
key: 'recordCurrency',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listRecordCurrencies',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Milestone',
|
||||||
|
key: 'milestone',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listMilestones',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Previous Task',
|
||||||
|
key: 'previousTask',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listTasks',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Parent Task',
|
||||||
|
key: 'parentTask',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listTasks',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Task Type',
|
||||||
|
key: 'taskType',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listTodoOptions',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.taskType',
|
||||||
|
value: 'tasktype',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Skipped Reason',
|
||||||
|
key: 'skippedReason',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listTodoOptions',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.skippedReason',
|
||||||
|
value: 'skipped_reason',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Estimate',
|
||||||
|
key: 'estimate',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Related Task',
|
||||||
|
key: 'relatedTask',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listTasks',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Project Name',
|
||||||
|
key: 'projectName',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listProjects',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Organization Name',
|
||||||
|
key: 'organizationName',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listOrganizations',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Send Email Reminder Before',
|
||||||
|
key: 'sendEmailReminderBefore',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Description',
|
||||||
|
key: 'description',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Is Billable',
|
||||||
|
key: 'isBillable',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
options: [
|
||||||
|
{ label: 'True', value: '1' },
|
||||||
|
{ label: 'False', value: '-1' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Service',
|
||||||
|
key: 'service',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listServices',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Rate',
|
||||||
|
key: 'rate',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'SLA Name',
|
||||||
|
key: 'slaName',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listSlaNames',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
@@ -0,0 +1,78 @@
|
|||||||
|
import defineAction from '../../../../helpers/define-action.js';
|
||||||
|
import { fields } from './fields.js';
|
||||||
|
|
||||||
|
export default defineAction({
|
||||||
|
name: 'Create todo',
|
||||||
|
key: 'createTodo',
|
||||||
|
description: 'Create a new todo.',
|
||||||
|
arguments: fields,
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
assignedTo,
|
||||||
|
startDateAndTime,
|
||||||
|
dueDate,
|
||||||
|
stage,
|
||||||
|
contactName,
|
||||||
|
priority,
|
||||||
|
sendNotification,
|
||||||
|
location,
|
||||||
|
recordCurrency,
|
||||||
|
milestone,
|
||||||
|
previousTask,
|
||||||
|
parentTask,
|
||||||
|
taskType,
|
||||||
|
skippedReason,
|
||||||
|
estimate,
|
||||||
|
relatedTask,
|
||||||
|
projectName,
|
||||||
|
organizationName,
|
||||||
|
sendEmailReminderBefore,
|
||||||
|
description,
|
||||||
|
isBillable,
|
||||||
|
service,
|
||||||
|
rate,
|
||||||
|
slaName,
|
||||||
|
} = $.step.parameters;
|
||||||
|
|
||||||
|
const elementData = {
|
||||||
|
subject: name,
|
||||||
|
assigned_user_id: assignedTo || $.auth.data.userId,
|
||||||
|
date_start: startDateAndTime,
|
||||||
|
due_date: dueDate,
|
||||||
|
taskstatus: stage,
|
||||||
|
contact_id: contactName,
|
||||||
|
taskpriority: priority,
|
||||||
|
sendnotification: sendNotification,
|
||||||
|
location: location,
|
||||||
|
record_currency_id: recordCurrency,
|
||||||
|
milestone: milestone,
|
||||||
|
dependent_on: previousTask,
|
||||||
|
parent_task: parentTask,
|
||||||
|
tasktype: taskType,
|
||||||
|
skipped_reason: skippedReason,
|
||||||
|
estimate: estimate,
|
||||||
|
related_task: relatedTask,
|
||||||
|
related_project: projectName,
|
||||||
|
account_id: organizationName,
|
||||||
|
reminder_time: sendEmailReminderBefore,
|
||||||
|
description: description,
|
||||||
|
is_billable: isBillable,
|
||||||
|
billing_service: service,
|
||||||
|
rate: rate,
|
||||||
|
slaid: slaName,
|
||||||
|
};
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
operation: 'create',
|
||||||
|
sessionName: $.auth.data.sessionName,
|
||||||
|
element: JSON.stringify(elementData),
|
||||||
|
elementType: 'Calendar',
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await $.http.post('/webservice.php', body);
|
||||||
|
|
||||||
|
$.setActionItem({ raw: response.data });
|
||||||
|
},
|
||||||
|
});
|
@@ -1,3 +1,4 @@
|
|||||||
import createOpportunity from './create-opportunity/index.js';
|
import createOpportunity from './create-opportunity/index.js';
|
||||||
|
import createTodo from './create-todo/index.js';
|
||||||
|
|
||||||
export default [createOpportunity];
|
export default [createOpportunity, createTodo];
|
||||||
|
@@ -1,13 +1,25 @@
|
|||||||
import listCampaignSources from './list-campaign-sources/index.js';
|
import listCampaignSources from './list-campaign-sources/index.js';
|
||||||
import listContacts from './list-contacts/index.js';
|
import listContacts from './list-contacts/index.js';
|
||||||
|
import listMilestones from './list-milestones/index.js';
|
||||||
import listOpportunityOptions from './list-opportunity-options/index.js';
|
import listOpportunityOptions from './list-opportunity-options/index.js';
|
||||||
import listOrganizations from './list-organizations/index.js';
|
import listOrganizations from './list-organizations/index.js';
|
||||||
|
import listProjects from './list-projects/index.js';
|
||||||
import listRecordCurrencies from './list-record-currencies/index.js';
|
import listRecordCurrencies from './list-record-currencies/index.js';
|
||||||
|
import listServices from './list-services/index.js';
|
||||||
|
import listSlaNames from './list-sla-names/index.js';
|
||||||
|
import listTasks from './list-tasks/index.js';
|
||||||
|
import listTodoOptions from './list-todo-options/index.js';
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
listCampaignSources,
|
listCampaignSources,
|
||||||
listContacts,
|
listContacts,
|
||||||
|
listMilestones,
|
||||||
listOpportunityOptions,
|
listOpportunityOptions,
|
||||||
listOrganizations,
|
listOrganizations,
|
||||||
|
listProjects,
|
||||||
listRecordCurrencies,
|
listRecordCurrencies,
|
||||||
|
listServices,
|
||||||
|
listSlaNames,
|
||||||
|
listTasks,
|
||||||
|
listTodoOptions,
|
||||||
];
|
];
|
||||||
|
@@ -0,0 +1,29 @@
|
|||||||
|
export default {
|
||||||
|
name: 'List milestones',
|
||||||
|
key: 'listMilestones',
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const milestones = {
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
operation: 'query',
|
||||||
|
sessionName: $.auth.data.sessionName,
|
||||||
|
query: 'SELECT * FROM ProjectMilestone ORDER BY createdtime DESC;',
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data } = await $.http.get('/webservice.php', { params });
|
||||||
|
|
||||||
|
if (data.result?.length) {
|
||||||
|
for (const milestone of data.result) {
|
||||||
|
milestones.data.push({
|
||||||
|
value: milestone.id,
|
||||||
|
name: milestone.projectmilestonename,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return milestones;
|
||||||
|
},
|
||||||
|
};
|
@@ -0,0 +1,29 @@
|
|||||||
|
export default {
|
||||||
|
name: 'List projects',
|
||||||
|
key: 'listProjects',
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const projects = {
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
operation: 'query',
|
||||||
|
sessionName: $.auth.data.sessionName,
|
||||||
|
query: 'SELECT * FROM Project ORDER BY createdtime DESC;',
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data } = await $.http.get('/webservice.php', { params });
|
||||||
|
|
||||||
|
if (data.result?.length) {
|
||||||
|
for (const project of data.result) {
|
||||||
|
projects.data.push({
|
||||||
|
value: project.id,
|
||||||
|
name: project.projectname,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return projects;
|
||||||
|
},
|
||||||
|
};
|
@@ -0,0 +1,29 @@
|
|||||||
|
export default {
|
||||||
|
name: 'List services',
|
||||||
|
key: 'listServices',
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const services = {
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
operation: 'query',
|
||||||
|
sessionName: $.auth.data.sessionName,
|
||||||
|
query: 'SELECT * FROM Services ORDER BY createdtime DESC;',
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data } = await $.http.get('/webservice.php', { params });
|
||||||
|
|
||||||
|
if (data.result?.length) {
|
||||||
|
for (const service of data.result) {
|
||||||
|
services.data.push({
|
||||||
|
value: service.id,
|
||||||
|
name: service.servicename,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return services;
|
||||||
|
},
|
||||||
|
};
|
@@ -0,0 +1,29 @@
|
|||||||
|
export default {
|
||||||
|
name: 'List sla names',
|
||||||
|
key: 'listSlaNames',
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const slaNames = {
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
operation: 'query',
|
||||||
|
sessionName: $.auth.data.sessionName,
|
||||||
|
query: 'SELECT * FROM SLA ORDER BY createdtime DESC;',
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data } = await $.http.get(`/webservice.php`, { params });
|
||||||
|
|
||||||
|
if (data.result?.length) {
|
||||||
|
for (const slaName of data.result) {
|
||||||
|
slaNames.data.push({
|
||||||
|
value: slaName.id,
|
||||||
|
name: slaName.policy_name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return slaNames;
|
||||||
|
},
|
||||||
|
};
|
@@ -0,0 +1,29 @@
|
|||||||
|
export default {
|
||||||
|
name: 'List tasks',
|
||||||
|
key: 'listTasks',
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const tasks = {
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
operation: 'query',
|
||||||
|
sessionName: $.auth.data.sessionName,
|
||||||
|
query: 'SELECT * FROM Calendar ORDER BY createdtime DESC;',
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data } = await $.http.get('/webservice.php', { params });
|
||||||
|
|
||||||
|
if (data.result?.length) {
|
||||||
|
for (const task of data.result) {
|
||||||
|
tasks.data.push({
|
||||||
|
value: task.id,
|
||||||
|
name: task.subject,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tasks;
|
||||||
|
},
|
||||||
|
};
|
@@ -0,0 +1,37 @@
|
|||||||
|
export default {
|
||||||
|
name: 'List todo options',
|
||||||
|
key: 'listTodoOptions',
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const todoOptions = {
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
const stage = $.step.parameters.stage;
|
||||||
|
const taskType = $.step.parameters.taskType;
|
||||||
|
const skippedReason = $.step.parameters.skippedReason;
|
||||||
|
const picklistFields = [stage, taskType, skippedReason];
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
operation: 'describe',
|
||||||
|
sessionName: $.auth.data.sessionName,
|
||||||
|
elementType: 'Calendar',
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data } = await $.http.get('/webservice.php', { params });
|
||||||
|
|
||||||
|
if (data.result.fields?.length) {
|
||||||
|
for (const field of data.result.fields) {
|
||||||
|
if (picklistFields.includes(field.name)) {
|
||||||
|
field.type.picklistValues.map((item) =>
|
||||||
|
todoOptions.data.push({
|
||||||
|
value: item.value,
|
||||||
|
name: item.label,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return todoOptions;
|
||||||
|
},
|
||||||
|
};
|
@@ -3,6 +3,8 @@ favicon: /favicons/vtiger-crm.svg
|
|||||||
items:
|
items:
|
||||||
- name: Create opportunity
|
- name: Create opportunity
|
||||||
desc: Create a new opportunity.
|
desc: Create a new opportunity.
|
||||||
|
- name: Create todo
|
||||||
|
desc: Create a new todo.
|
||||||
---
|
---
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
Reference in New Issue
Block a user