Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
pontusab committed Nov 18, 2024
1 parent 59be8d3 commit 56405fc
Show file tree
Hide file tree
Showing 21 changed files with 465 additions and 108 deletions.
8 changes: 0 additions & 8 deletions apps/dashboard/jobs/invoice/scheduler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,6 @@ export const invoiceScheduler = schedules.task({

if (!invoices) return;

await checkInvoiceStatus.batchTrigger(
invoices.map((invoice) => ({
payload: {
invoiceId: invoice.id,
},
})),
);

logger.info("Invoice status check jobs started", {
count: invoices.length,
});
Expand Down
27 changes: 27 additions & 0 deletions apps/dashboard/src/actions/create-tag-action.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"use server";

import { LogEvents } from "@midday/events/events";
import { authActionClient } from "./safe-action";
import { createTagSchema } from "./schema";

export const createTagAction = authActionClient
.schema(createTagSchema)
.metadata({
name: "create-tag",
track: {
event: LogEvents.CreateTag.name,
channel: LogEvents.CreateTag.channel,
},
})
.action(async ({ parsedInput: { name }, ctx: { user, supabase } }) => {
const { data } = await supabase
.from("tags")
.insert({
name,
team_id: user.team_id!,
})
.select("id, name")
.single();

return data;
});
23 changes: 0 additions & 23 deletions apps/dashboard/src/actions/create-tags-action.ts

This file was deleted.

32 changes: 32 additions & 0 deletions apps/dashboard/src/actions/create-tracker-project-tag-action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"use server";

import { LogEvents } from "@midday/events/events";
import { revalidateTag } from "next/cache";
import { authActionClient } from "./safe-action";
import { createTrackerProjectTagSchema } from "./schema";

export const createTrackerProjectTagAction = authActionClient
.schema(createTrackerProjectTagSchema)
.metadata({
name: "create-tracker-project-tag",
track: {
event: LogEvents.CreateTrackerProjectTag.name,
channel: LogEvents.CreateTrackerProjectTag.channel,
},
})
.action(
async ({
parsedInput: { tagId, trackerProjectId },
ctx: { user, supabase },
}) => {
const { data } = await supabase.from("tracker_project_tags").insert({
tag_id: tagId,
tracker_project_id: trackerProjectId,
team_id: user.team_id!,
});

revalidateTag(`tracker_projects_${user.team_id}`);

return data;
},
);
32 changes: 32 additions & 0 deletions apps/dashboard/src/actions/create-transaction-tag-action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"use server";

import { LogEvents } from "@midday/events/events";
import { revalidateTag } from "next/cache";
import { authActionClient } from "./safe-action";
import { createTransactionTagSchema } from "./schema";

export const createTransactionTagAction = authActionClient
.schema(createTransactionTagSchema)
.metadata({
name: "create-transaction-tag",
track: {
event: LogEvents.CreateTransactionTag.name,
channel: LogEvents.CreateTransactionTag.channel,
},
})
.action(
async ({
parsedInput: { tagId, transactionId },
ctx: { user, supabase },
}) => {
const { data } = await supabase.from("transaction_tags").insert({
tag_id: tagId,
transaction_id: transactionId,
team_id: user.team_id!,
});

revalidateTag(`transactions_${user.team_id}`);

return data;
},
);
32 changes: 32 additions & 0 deletions apps/dashboard/src/actions/delete-transaction-tag-action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"use server";

import { LogEvents } from "@midday/events/events";
import { revalidateTag } from "next/cache";
import { authActionClient } from "./safe-action";
import { deleteTransactionTagSchema } from "./schema";

export const deleteTransactionTagAction = authActionClient
.schema(deleteTransactionTagSchema)
.metadata({
name: "delete-transaction-tag",
track: {
event: LogEvents.DeleteTransactionTag.name,
channel: LogEvents.DeleteTransactionTag.channel,
},
})
.action(
async ({
parsedInput: { tagId, transactionId },
ctx: { user, supabase },
}) => {
const { data } = await supabase
.from("transaction_tags")
.delete()
.eq("transaction_id", transactionId)
.eq("tag_id", tagId);

revalidateTag(`transactions_${user.team_id}`);

return data;
},
);
16 changes: 15 additions & 1 deletion apps/dashboard/src/actions/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,21 @@ export const updateUserSchema = z.object({
revalidatePath: z.string().optional(),
});

export const createTagsSchema = z.array(z.object({ name: z.string() }));
export const createTagSchema = z.object({ name: z.string() });
export const createTransactionTagSchema = z.object({
tagId: z.string(),
transactionId: z.string(),
});

export const deleteTransactionTagSchema = z.object({
tagId: z.string(),
transactionId: z.string(),
});

export const createTrackerProjectTagSchema = z.object({
tagId: z.string(),
trackerProjectId: z.string(),
});

export type UpdateUserFormValues = z.infer<typeof updateUserSchema>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export default async function Transactions({
statuses,
recurring,
accounts,
tags,
} = searchParamsCache.parse(searchParams);

// Move this in a suspense
Expand All @@ -59,6 +60,7 @@ export default async function Transactions({
statuses,
recurring,
accounts,
tags,
};

const sort = searchParams?.sort?.split(":");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const searchParamsCache = createSearchParamsCache({
start: parseAsString,
end: parseAsString,
categories: parseAsArrayOf(parseAsString),
tags: parseAsArrayOf(parseAsString),
accounts: parseAsArrayOf(parseAsString),
assignees: parseAsArrayOf(parseAsString),
recurring: parseAsArrayOf(
Expand Down
29 changes: 28 additions & 1 deletion apps/dashboard/src/components/forms/tracker-project-form.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client";

import { createTrackerProjectTagAction } from "@/actions/create-tracker-project-tag-action";
import type { Customer } from "@/components/invoice/customer-details";
import { uniqueCurrencies } from "@midday/location/currencies";
import { Button } from "@midday/ui/button";
Expand All @@ -26,6 +27,7 @@ import {
import { Switch } from "@midday/ui/switch";
import { Textarea } from "@midday/ui/textarea";
import { Loader2 } from "lucide-react";
import { useAction } from "next-safe-action/hooks";
import { useEffect, useState } from "react";
import { SearchCustomer } from "../search-customer";
import { SelectTags } from "../select-tags";
Expand All @@ -44,6 +46,7 @@ export function TrackerProjectForm({
customers,
}: Props) {
const [isOpen, setIsOpen] = useState(false);
const createTrackerProjectTag = useAction(createTrackerProjectTagAction);

useEffect(() => {
setIsOpen(Boolean(form.getValues("billable")));
Expand Down Expand Up @@ -101,7 +104,31 @@ export function TrackerProjectForm({
Expense Tags
</Label>

<SelectTags />
<SelectTags
// onSelect={(tagId) =>
// createTrackerProjectTag.execute({
// tagId,
// trackerProjectId: form.getValues("id"),
// })
// }
// tags={data?.tags?.map((tag) => ({
// label: tag.tag.name,
// value: tag.tag.name,
// id: tag.tag.id,
// }))}
onSelect={(tag) => {
createTrackerProjectTag.execute({
tagId: tag.id,
trackerProjectId: form.getValues("id"),
});
}}
onRemove={(tag) => {
createTrackerProjectTag.execute({
tagId: tag.id,
trackerProjectId: form.getValues("id"),
});
}}
/>

<FormDescription className="mt-2">
Tags help categorize and track project expenses.
Expand Down
58 changes: 38 additions & 20 deletions apps/dashboard/src/components/select-tags.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,33 @@
import { createTagsAction } from "@/actions/create-tags-action";
import { createTagAction } from "@/actions/create-tag-action";
import { useUserContext } from "@/store/user/hook";
import { createClient } from "@midday/supabase/client";
import { getTransactionTagsQuery } from "@midday/supabase/queries";
import { getTagsQuery } from "@midday/supabase/queries";
import MultipleSelector, { type Option } from "@midday/ui/multiple-selector";
import { useAction } from "next-safe-action/hooks";
import React, { useEffect, useState } from "react";

type Props = {
tags?: Option[];
onSelect?: (tag: Option) => void;
onRemove?: (tag: Option & { id: string }) => void;
};

export function SelectTags({ tags }: Props) {
const [isLoading, setIsLoading] = useState(false);
const [data, setData] = useState<Option[]>(tags ?? []);
const createTags = useAction(createTagsAction);
export function SelectTags({ tags, onSelect, onRemove }: Props) {
const supabase = createClient();

const [data, setData] = useState<Option[]>(tags ?? []);
const [selected, setSelected] = useState<Option[]>(tags ?? []);
const createTag = useAction(createTagAction, {
onSuccess: ({ data }) => {
onSelect(data);
},
});

const { team_id: teamId } = useUserContext((state) => state.data);

useEffect(() => {
async function fetchData() {
setIsLoading(true);

const { data } = await getTransactionTagsQuery(supabase, teamId);
const { data } = await getTagsQuery(supabase, teamId);

if (data?.length) {
setData(
Expand All @@ -32,30 +38,42 @@ export function SelectTags({ tags }: Props) {
})),
);
}

setIsLoading(false);
}

if (!data?.length) {
fetchData();
}
fetchData();
}, [teamId]);

return (
<div className="w-full">
<MultipleSelector
// key={isLoading ? "loading" : "loaded"}
options={data}
value={selected}
placeholder="Select tags"
creatable
emptyIndicator={<p className="text-sm">no results found.</p>}
emptyIndicator={<p className="text-sm">No results found.</p>}
onCreate={(option) => {
createTag.execute({ name: option.value });
}}
onChange={(options) => {
const newTags = options.filter((option) => option.create);
setSelected(options);

const newTag = options.find(
(tag) => !selected.find((opt) => opt.value === tag.value),
);

if (newTag) {
onSelect?.(newTag);
return;
}

console.log(newTags);
if (options.length < selected.length) {
const removedTag = selected.find(
(tag) => !options.find((opt) => opt.value === tag.value),
);

if (newTags.length > 0) {
createTags.execute(newTags.map((tag) => ({ name: tag.value })));
if (removedTag) {
onRemove?.(removedTag);
}
}
}}
/>
Expand Down
13 changes: 13 additions & 0 deletions apps/dashboard/src/components/tables/tracker/data-table-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,19 @@ export function DataTableHeader() {
)}
</Button>
</TableHead>

<TableHead className="min-w-[160px]">
<Button
className="p-0 hover:bg-transparent space-x-2"
variant="ghost"
onClick={() => createSortQuery("tags")}
>
<span>Tags</span>
{"tags" === column && value === "asc" && <ArrowDown size={16} />}
{"tags" === column && value === "desc" && <ArrowUp size={16} />}
</Button>
</TableHead>

<TableHead className="w-[140px]">
<Button
className="p-0 hover:bg-transparent space-x-2"
Expand Down
Loading

0 comments on commit 56405fc

Please sign in to comment.