# `GanttEx.TaskForm`
[🔗](https://github.com/rocket4ce/gantt_ex/blob/v0.1.0/lib/gantt_ex/task_form.ex#L1)

Converts tasks to lightbox form params and validates submitted params.

The lightbox uses plain HTML inputs, so dates travel as `"YYYY-MM-DD"`
(or `"YYYY-MM-DDTHH:MM"` when the duration unit is smaller than a day) and
progress as a percentage.

    iex> task = GanttEx.Task.new!(id: 1, text: "Design", start_date: ~D[2025-01-06], duration: 3, progress: 0.4)
    iex> GanttEx.TaskForm.params(task, :day)
    %{"text" => "Design", "type" => "task", "start_date" => "2025-01-06", "duration" => "3", "progress" => "40", "parent" => "", "constraint_type" => "", "constraint_date" => "", "planned_start" => "", "planned_end" => "", "deadline" => ""}

    iex> GanttEx.TaskForm.cast(%{"text" => " Build ", "type" => "task", "start_date" => "2025-01-08", "duration" => "2", "progress" => "25"}, :day)
    {:ok, %{text: "Build", type: :task, start_date: ~N[2025-01-08 00:00:00], duration: 2, progress: 0.25}}

    iex> GanttEx.TaskForm.cast(%{"text" => "", "start_date" => "nope", "duration" => "-1"}, :day)
    {:error, [text: :required, start_date: :invalid_date, duration: :invalid_number]}

# `cast`

```elixir
@spec cast(map(), GanttEx.Dates.unit(), GanttEx.Store.t() | nil) ::
  {:ok, map()} | {:error, keyword(atom())}
```

Validates submitted params.

Returns `{:ok, attrs}` with the attributes to apply to the task, or
`{:error, errors}` where errors is a keyword list of `field: reason`. Reasons
are keys of `GanttEx.I18n.labels/1` (`:required`, `:invalid_date`,
`:invalid_number`, `:invalid_progress`).

A `"parent"` param is resolved to a task id when a `store` is given (an empty
value means "top level").

# `date_input_type`

```elixir
@spec date_input_type(GanttEx.Dates.unit()) :: String.t()
```

Returns the HTML input type used for dates with the given duration unit.

    iex> GanttEx.TaskForm.date_input_type(:day)
    "date"

    iex> GanttEx.TaskForm.date_input_type(:hour)
    "datetime-local"

# `display_end`

```elixir
@spec display_end(GanttEx.Task.t(), GanttEx.Dates.unit()) :: NaiveDateTime.t() | nil
```

Returns the end date to *display*.

End dates are exclusive internally (a one-day task on January 6th ends on
January 7th at midnight), but people expect to read the last day of work.
For day-based units, an end date at midnight is shown as the previous day.

    iex> task = GanttEx.Task.new!(id: 1, start_date: ~D[2025-01-06], end_date: ~D[2025-01-07])
    iex> GanttEx.TaskForm.display_end(task, :day)
    ~N[2025-01-06 00:00:00]

    iex> task = GanttEx.Task.new!(id: 1, start_date: ~N[2025-01-06 09:00:00], end_date: ~N[2025-01-06 17:00:00])
    iex> GanttEx.TaskForm.display_end(task, :hour)
    ~N[2025-01-06 17:00:00]

# `inclusive_end`

```elixir
@spec inclusive_end(
  NaiveDateTime.t() | nil,
  NaiveDateTime.t() | nil,
  GanttEx.Dates.unit()
) ::
  NaiveDateTime.t() | nil
```

Like `display_end/2` for any end date (such as `:planned_end`).

    iex> GanttEx.TaskForm.inclusive_end(~N[2025-01-08 00:00:00], ~N[2025-01-06 00:00:00], :day)
    ~N[2025-01-07 00:00:00]

    iex> GanttEx.TaskForm.inclusive_end(nil, nil, :day)
    nil

# `input_date`

```elixir
@spec input_date(NaiveDateTime.t() | nil, GanttEx.Dates.unit()) :: String.t()
```

Formats a datetime for a date input.

    iex> GanttEx.TaskForm.input_date(~N[2025-01-06 09:30:00], :hour)
    "2025-01-06T09:30"

# `params`

```elixir
@spec params(GanttEx.Task.t(), GanttEx.Dates.unit()) :: %{
  required(String.t()) =&gt; String.t()
}
```

Returns the form params for a task.

# `parse_end`

```elixir
@spec parse_end(NaiveDateTime.t(), GanttEx.Dates.unit()) :: NaiveDateTime.t()
```

Converts a displayed (inclusive) end date back to the exclusive end date.
The inverse of `display_end/2`.

    iex> GanttEx.TaskForm.parse_end(~N[2025-01-06 00:00:00], :day)
    ~N[2025-01-07 00:00:00]

    iex> GanttEx.TaskForm.parse_end(~N[2025-01-06 17:00:00], :hour)
    ~N[2025-01-06 17:00:00]

---

*Consult [api-reference.md](api-reference.md) for complete listing*
