feat: status selectors update
This commit is contained in:
@@ -80,6 +80,8 @@ import {
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { ColumnSelectorDialog } from "@/components/column-selector-dialog";
|
||||
import { CreativeSelectDialog } from "@/components/creative-select-dialog";
|
||||
import { PlacementStatusSelector } from "@/components/placement-status-selector";
|
||||
import { PlacementPostStatusSelector } from "@/components/placement-post-status-selector";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type {
|
||||
Project,
|
||||
@@ -98,10 +100,12 @@ import {
|
||||
PLACEMENT_DEAL_STATUSES,
|
||||
PLACEMENT_POST_STATUSES,
|
||||
canEditPostStatus,
|
||||
getPlacementStatusColor,
|
||||
} from "@/lib/config/placement-columns";
|
||||
|
||||
type PlacementInlineDraft = {
|
||||
status?: PlacementStatus;
|
||||
post_status?: PlacementPostStatus | null;
|
||||
comment?: string | null;
|
||||
creative_id?: string | null;
|
||||
creative_name?: string | null;
|
||||
@@ -114,7 +118,7 @@ type PlacementInlineDraft = {
|
||||
cpm?: number | null;
|
||||
};
|
||||
|
||||
type EditableField = "status" | "placement_at" | "payment_at" | "cost" | "cost_before_bargain" | "format" | "comment" | "cost_format" | "cpm" | "creative_id" | "creative_name";
|
||||
type EditableField = "status" | "post_status" | "placement_at" | "payment_at" | "cost" | "cost_before_bargain" | "format" | "comment" | "cost_format" | "cpm" | "creative_id" | "creative_name";
|
||||
|
||||
const FIXED_COST_TYPE: CostType = "fixed";
|
||||
|
||||
@@ -262,25 +266,6 @@ function CellRenderer({
|
||||
}: CellRendererProps) {
|
||||
const isEditing = editingCell?.placementId === placement.id;
|
||||
|
||||
const getStatusColor = (status: PlacementStatus): string => {
|
||||
switch (status) {
|
||||
case "Оплачено":
|
||||
return "bg-emerald-100 text-emerald-700 border-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-400 dark:border-emerald-800";
|
||||
case "Согласование условий":
|
||||
case "Ждём ответа":
|
||||
return "bg-amber-100 text-amber-700 border-amber-200 dark:bg-amber-900/30 dark:text-amber-400 dark:border-amber-800";
|
||||
case "Отмена":
|
||||
case "Не подходит цена":
|
||||
case "Неактуально":
|
||||
case "Не отвечает":
|
||||
return "bg-red-100 text-red-700 border-red-200 dark:bg-red-900/30 dark:text-red-400 dark:border-red-800";
|
||||
case "Оплатить":
|
||||
return "bg-blue-100 text-blue-700 border-blue-200 dark:bg-blue-900/30 dark:text-blue-400 dark:border-blue-800";
|
||||
default:
|
||||
return "bg-muted text-muted-foreground border-border";
|
||||
}
|
||||
};
|
||||
|
||||
const formatCurrency = (value: number): string => {
|
||||
return new Intl.NumberFormat("ru-RU", {
|
||||
style: "currency",
|
||||
@@ -358,43 +343,14 @@ function CellRenderer({
|
||||
case "status_deal":
|
||||
return (
|
||||
<td key={column.id} className="py-2 px-3">
|
||||
{isEditing && editingCell?.field === "status" ? (
|
||||
<select
|
||||
aria-label="Статус размещения"
|
||||
className={cn(
|
||||
"h-7 w-full rounded-md border border-input bg-background px-2 text-xs font-medium shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
||||
getStatusColor(placement.status)
|
||||
)}
|
||||
value={placement.status}
|
||||
onChange={(e) =>
|
||||
handleDraftChange(placement.id, {
|
||||
status: e.target.value as PlacementStatus,
|
||||
})
|
||||
}
|
||||
onBlur={() => setEditingCell(null)}
|
||||
>
|
||||
{PLACEMENT_DEAL_STATUSES.map((status) => (
|
||||
<option key={status} value={status}>
|
||||
{status}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
disabled={!canEdit}
|
||||
className={cn(
|
||||
"group h-7 w-full rounded-md border px-2 text-xs font-medium shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-default flex items-center justify-between gap-2",
|
||||
getStatusColor(placement.status)
|
||||
)}
|
||||
onClick={() =>
|
||||
canEdit && setEditingCell({ placementId: placement.id, field: "status" })
|
||||
}
|
||||
>
|
||||
<span>{placement.status}</span>
|
||||
<Pencil className="h-3 w-3 opacity-0 group-hover:opacity-70 transition-opacity" />
|
||||
</button>
|
||||
)}
|
||||
<PlacementStatusSelector
|
||||
status={placement.status}
|
||||
isEditing={isEditing && editingCell?.field === "status"}
|
||||
canEdit={canEdit}
|
||||
onEdit={() => canEdit && setEditingCell({ placementId: placement.id, field: "status" })}
|
||||
onChange={(s) => handleDraftChange(placement.id, { status: s })}
|
||||
onCloseEdit={() => setEditingCell(null)}
|
||||
/>
|
||||
</td>
|
||||
);
|
||||
|
||||
@@ -402,13 +358,14 @@ function CellRenderer({
|
||||
const postStatus = placement.placement_post?.status;
|
||||
return (
|
||||
<td key={column.id} className="py-2 px-3">
|
||||
{postStatus ? (
|
||||
<span className="text-xs px-2 py-1 rounded border inline-block">
|
||||
{postStatus}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-muted-foreground">—</span>
|
||||
)}
|
||||
<PlacementPostStatusSelector
|
||||
status={postStatus ?? null}
|
||||
isEditing={isEditing && editingCell?.field === "post_status"}
|
||||
canEdit={canEdit}
|
||||
onEdit={() => canEdit && setEditingCell({ placementId: placement.id, field: "post_status" })}
|
||||
onChange={(s) => handleDraftChange(placement.id, { post_status: s })}
|
||||
onCloseEdit={() => setEditingCell(null)}
|
||||
/>
|
||||
</td>
|
||||
);
|
||||
|
||||
@@ -1070,36 +1027,36 @@ function ChannelGroup({
|
||||
? (effectiveCost / viewPlacement.placement_post.views_count) * 1000
|
||||
: null;
|
||||
|
||||
return (
|
||||
<tr
|
||||
key={placement.id}
|
||||
data-placement-id={placement.id}
|
||||
className="border-b last:border-0 hover:bg-muted/30"
|
||||
>
|
||||
{ALL_COLUMNS.filter((c) => visibleColumns.has(c.id)).map((column) => (
|
||||
<CellRenderer
|
||||
key={column.id}
|
||||
column={column}
|
||||
placement={viewPlacement}
|
||||
idx={idx}
|
||||
channelNumber={channelNumber}
|
||||
canEdit={canEdit}
|
||||
editingCell={editingCell}
|
||||
setEditingCell={setEditingCell}
|
||||
handleDraftChange={handleDraftChange}
|
||||
effectiveCost={effectiveCost}
|
||||
cpf={cpf}
|
||||
cpm={cpm}
|
||||
selectedPlacementIds={selectedPlacementIds}
|
||||
onTogglePlacement={onTogglePlacement}
|
||||
onOpenCreativeSelect={onOpenCreativeSelect}
|
||||
onClearCreative={onClearCreative}
|
||||
onOpenCreativeSend={onOpenCreativeSend}
|
||||
/>
|
||||
))}
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
return (
|
||||
<tr
|
||||
key={placement.id}
|
||||
data-placement-id={placement.id}
|
||||
className="border-b last:border-0 hover:bg-muted/30"
|
||||
>
|
||||
{ALL_COLUMNS.filter((c) => visibleColumns.has(c.id)).map((column) => (
|
||||
<CellRenderer
|
||||
key={column.id}
|
||||
column={column}
|
||||
placement={viewPlacement}
|
||||
idx={idx}
|
||||
channelNumber={channelNumber}
|
||||
canEdit={canEdit}
|
||||
editingCell={editingCell}
|
||||
setEditingCell={setEditingCell}
|
||||
handleDraftChange={handleDraftChange}
|
||||
effectiveCost={effectiveCost}
|
||||
cpf={cpf}
|
||||
cpm={cpm}
|
||||
selectedPlacementIds={selectedPlacementIds}
|
||||
onTogglePlacement={onTogglePlacement}
|
||||
onOpenCreativeSelect={onOpenCreativeSelect}
|
||||
onClearCreative={onClearCreative}
|
||||
onOpenCreativeSend={onOpenCreativeSend}
|
||||
/>
|
||||
))}
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -1313,6 +1270,10 @@ export default function PurchasePlanDetailPage() {
|
||||
const nextStatus = draft.status ?? placement.status;
|
||||
const nextComment = draft.comment !== undefined ? draft.comment : placement.comment;
|
||||
|
||||
const nextPlacementPost = draft.post_status !== null && draft.post_status !== undefined && placement.placement_post
|
||||
? { ...placement.placement_post, status: draft.post_status }
|
||||
: placement.placement_post;
|
||||
|
||||
const nextDetails = {
|
||||
...(placement.details || {
|
||||
placement_at: null,
|
||||
@@ -1357,6 +1318,7 @@ export default function PurchasePlanDetailPage() {
|
||||
return {
|
||||
...placement,
|
||||
status: nextStatus,
|
||||
placement_post: nextPlacementPost,
|
||||
comment: nextComment,
|
||||
creative_id: draft.creative_id ?? placement.creative_id,
|
||||
creative_name: draft.creative_name ?? placement.creative_name,
|
||||
|
||||
Reference in New Issue
Block a user