A bulk import can partially succeed, so I wanted the result screen to explain every row instead of reducing the whole file to success or failure.

A CSV upload can finish successfully while one of its rows never becomes a shift.
That makes a message like this almost useless:
Import complete
Complete in what sense?
If the file contained 40 assigned shifts, I wanted to know whether all 40 were created, whether three were rejected, or whether one was imported with something the owner should double-check.
That pushed me to think about the import result as a debugging surface rather than a confirmation toast.
The file isn't the smallest useful unit
For this import, one CSV row represents one assigned shift.
A simplified row needs things like:
shift_date
start_time_local
end_time_local
assigned_member_email
client_name
address_full
There are optional fields too, but those required values are enough to show the problem.
Imagine a file with three rows:
Row 2
Maria
maria@example.com
May 12, 9:00 AM
Maple Street home
Row 3
Jordan
jordan@example.com
May 12, 9:00 AM
Oak Street office
Row 4
Sam
sam-old@example.com
May 13, 6:00 PM
Pine Avenue clinic
Rows 2 and 3 may be perfectly usable.
Row 4 may fail because sam-old@example.com does not match an active team member in the workspace.
Rejecting the entire file would make the owner repair one row and then resubmit work that was already valid.
Pretending the whole file succeeded would be worse. Now the owner may assume Sam has a shift that was never created.
So the useful result isn't:
File: success
It's closer to:
Row 2: Imported
Row 3: Imported
Row 4: Failed — Staff email not found
That changes the level at which the UI needs to explain failure.
I separated Failed from Warning
Not every suspicious row should behave like an invalid row.
A scheduling conflict is a good example.
Suppose two assignments overlap for the same cleaner.
That deserves attention, but I don't necessarily want the import tool deciding that the new shift is invalid. The schedule may intentionally contain overlap while an owner is still resolving staffing, or the warning may need human judgment.
So the current import flow treats that case differently:
Imported
Warning
Failed
A Failed row is skipped.
A Warning row is still imported, but something about it should be reviewed.
That distinction matters because status determines whether a shift actually exists after the import.
If both cases were displayed as a generic red error, the owner would still have to guess whether the row was created.
If both were allowed through without explanation, the warning would disappear at exactly the moment it was most useful.
The CSV import guide reflects that distinction directly: valid rows create shifts, invalid rows are skipped, and detected scheduling conflicts remain imported with a warning.
The reason column does more work than the status
A row labeled Failed still leaves another question:
Why?
The result needs enough context to make the next fix obvious.
For the current flow, the useful row-level output includes information such as:
Row number
Assigned staff email
Shift date and time
Client name
Status
Reason
That means someone can look at:
Row 4
sam-old@example.com
May 13 · 6:00 PM
Pine Avenue clinic
Failed
Staff email not found
and go directly back to the corresponding spreadsheet row.
They don't have to infer which of 40 assignments caused the problem.
I like this because debugging a bulk operation should not require reproducing the whole operation mentally.
The result screen already knows which row produced which outcome. Hiding that information behind a generic failure message just transfers the debugging work to the user.
Partial success also creates a retry problem
Once valid rows can succeed while invalid rows fail, retry behavior becomes important.
The current CSV import creates new shifts. It does not overwrite existing ones, and it does not automatically remove duplicates.
That means uploading the exact same file again is not a harmless retry.
If 39 rows succeeded and one failed, fixing that one cell and importing all 40 rows again may create another copy of the 39 successful shifts.
So the debugging surface has another job: make partial success visible enough that the owner knows what already happened.
This is why I don't want the result page to disappear behind a green toast.
The output is part of the import operation.
It tells the user what is safe to fix next.
For the current version, the practical rule is intentionally simple: review the file before importing it again rather than assuming a repeated upload is idempotent.
I could imagine a more advanced importer eventually supporting deduplication keys, dry runs, row selection, or safe retries.
I'm not claiming those exist now.
Clear row-level results solve the smaller problem first.
Silent import was another reason to make the result explicit
Imported shifts are silent by default.
An owner or admin can choose whether imported staff should receive email notifications, but notifications are not something I want quietly coupled to every upload.
That makes the import result especially important.
The upload is not just “I sent a spreadsheet somewhere.”
It may have created dozens of real Unconfirmed shifts.
Some rows may have failed.
Some may carry warnings.
And depending on the selected option, staff notifications may or may not follow.
The UI after the operation needs to make that outcome legible before the owner moves on.
A bulk action deserves a receipt
I used to think of import feedback mostly as validation:
tell the user when something is wrong.
Now I think the more useful model is a receipt.
For each input row:
What did we receive?
What did we do with it?
Did a shift get created?
Is anything worth checking?
If it failed, why?
That is much easier to debug than one message attached to an entire file.
A CSV importer can save someone from creating 40 shifts manually.
But if it turns one bad row into a mystery, it gives some of that saved time right back.
The result screen is not cleanup around the import.
It is part of the feature.
Top comments (0)