../courses.php
CRM
CRM101
90
Tasks, Reminders, And Pipelines
Learn how CRM systems turn customer work into organized next steps.
A note recorded "Called Tuesday, interested" — a record of something that already happened. A task recorded "Follow up Friday" describes something that hasn't happened yet. Why does that one difference mean tasks need their own field, separate from notes, instead of just being another kind of note?
A note is a closed record — it stays true forever once written. A task has a state that needs to change: it starts not-done, and at some point becomes done, and that state needs to be checked and updated, not just read back. Mixing tasks into the same array as notes would mean every note-reading loop also has to figure out which entries are actually tasks and which have already been completed — keeping them as their own field with their own
done
flag keeps both kinds of data doing one clear job each.
A note records what happened. A task records what should happen next, and whether it has happened yet. The two answer different questions, which is exactly why they need their own separate piece of structure, not a shared one.
A task needs at least three things: what to do, when, and whether it's done yet — a small object, the same shape pattern notes already used, just with a boolean field added for state that's actually meant to change over time.
Marking a task done means finding that specific task inside a specific contact's tasks array and flipping its
done
field from
false
to
true
— modifying one specific nested value, not adding a new one, the first time this course updates existing data rather than only appending to it.
A pipeline is really just several possible task or status stages in sequence — lead, contacted, follow-up scheduled, customer — the same status-field idea from lecture 3, extended to more than two possible values.
This lesson adds a tasks array alongside the notes array on each contact, with a way to add a task and a way to mark one done — the first time this course modifies an existing piece of nested data instead of only appending to it.
The CRM, stage five: tasks that can actually be marked done
<?php
$contactsFile = __DIR__ . '/contacts.json';
$contacts = json_decode(file_get_contents($contactsFile), true);
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$index = $_POST['contact_index'];
if (isset($_POST['new_task'])) {
$contacts[$index]['tasks'][] = [
'text' => $_POST['task_text'],
'done' => false
];
}
if (isset($_POST['complete_task'])) {
$taskIndex = $_POST['task_index'];
$contacts[$index]['tasks'][$taskIndex]['done'] = true;
}
file_put_contents($contactsFile, json_encode($contacts));
}
?>
<?php foreach ($contacts as $i => $contact) { ?>
<h3><?php echo $contact['name']; ?></h3>
<?php foreach ($contact['tasks'] as $t => $task) { ?>
<p>
<?php echo $task['done'] ? '[done] ' : '[ ] '; ?>
<?php echo $task['text']; ?>
<?php if (!$task['done']) { ?>
<form method="POST" style="display:inline">
<input type="hidden" name="contact_index" value="<?php echo $i; ?>">
<input type="hidden" name="task_index" value="<?php echo $t; ?>">
<button type="submit" name="complete_task">Mark Done</button>
</form>
<?php } ?>
</p>
<?php } ?>
<?php } ?>
'done' => false — Every new task starts undone, deliberately — this is the field that's actually meant to change later, unlike anything in a note.
isset($_POST['new_task'])
and
isset($_POST['complete_task']) — Two different submit buttons, each with its own name, let one PHP block tell which of two different actions actually triggered this particular form submission.
$contacts[$index]['tasks'][$taskIndex]['done'] = true; — This is the new pattern in this lesson: not appending a new value, but reaching directly into an existing array position and changing one field on what's already there.
foreach ($contact['tasks'] as $t => $task) — Captures each task's own index within this specific contact's tasks array, the same way contact indexes were captured in the previous lesson — needed here to identify exactly which task gets marked done.
<?php if (!$task['done']) { ?> — The "Mark Done" button only renders for tasks that aren't already done — once a task is complete, there's nothing left to do with it in this interface, so no button appears.
Mixing notes and tasks into one array would force every reader to re-sort them
If tasks and notes shared one array, displaying "just the open tasks" or "just the history" would mean filtering that mixed array every single time, by some marker distinguishing the two — keeping them in separate fields from the start avoids that extra filtering work entirely.
Two named submit buttons let one form-handling block do two different things safely
Without checking which specific button was pressed, there'd be no way to tell "add a new task" apart from "mark an existing one done" inside the same PHP block — the two named buttons are what make that distinction possible from a single page's form submissions.
An invalid task index would silently fail or corrupt unrelated data
If
$taskIndex
referred to a position that doesn't actually exist in that contact's tasks array, PHP would create a new, oddly-shaped entry rather than reporting a clear error — this is exactly the kind of mistake that benefits from the contract-and-test discipline covered elsewhere in this catalog's AI course.
A contact missing the tasks array entirely breaks this page the same way a missing notes array did before
Just like the previous lesson's notes array, any contact without a
tasks
field — even an empty one — will fail the inner
foreach
loop the moment that specific contact is reached.
A done/not-done flag is the simplest possible version of a pipeline stage
Expanding
done
from a true/false flag into a multi-value status field — not started, in progress, done — is exactly the same idea as lecture 3's lead/customer status, just applied to an individual task instead of a whole contact.
Tasks mixed into the same array as notes — avoid
"notes": [
{ "text": "Called Tuesday, interested" },
{ "text": "Follow up Friday", "done": false }
]
Tasks and notes kept in their own separate fields — use this
"notes": [{ "text": "Called Tuesday, interested" }],
"tasks": [{ "text": "Follow up Friday", "done": false }]
Paste your task-completion PHP code into ChatGPT and ask: "What happens here if the submitted task_index doesn't actually correspond to an existing task on this contact?" It's good at tracing that exact failure path, though testing it directly with a deliberately wrong index is worth doing too.
Add an empty
"tasks": []
array to every contact in your JSON file.
Add the new-task form and confirm a new task appears, undone, under the correct contact.
Add the "Mark Done" button and confirm clicking it actually flips that specific task's done field to true.
Confirm the "Mark Done" button disappears once a task is actually marked done.
Add two tasks to the same contact, mark only one done, and confirm the other one is unaffected.
Mixing tasks and notes into one shared array instead of giving each its own field, forcing extra filtering logic every time either one needs to be read separately.
Using a single submit button for two different actions, with no way for the PHP handling it to tell which action was actually intended.
Trusting a submitted task index without checking it actually corresponds to a real task, risking a corrupted or oddly-shaped entry.
Adding a tasks field to new contacts going forward without adding it retroactively to existing ones, breaking the display loop for older records.
Treating done/not-done as the only possible task state forever, instead of recognizing it as the simplest version of a richer pipeline stage covered conceptually in lecture 3.
Model a task with its own changeable state separate from a note's fixed history, modify one specific nested value inside an existing record, and distinguish two different form actions submitted from the same page.
Login - Jit4All
Login
Welcome back to Jit4All