This commit is contained in:
David Arranz 2024-06-18 16:23:27 +02:00
parent 7f363cd64a
commit 1caa778374
12 changed files with 63 additions and 389 deletions

View File

@ -1,35 +1,4 @@
import {
Badge,
Button,
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/ui";
import { File, ListFilter, MoreHorizontal, PlusCircle } from "lucide-react";
import { DataTableProvider } from "@/lib/hooks";
import { Trans } from "react-i18next";
import { CatalogDataTable } from "./components";
export const CatalogList = () => {
@ -38,282 +7,4 @@ export const CatalogList = () => {
<CatalogDataTable />
</DataTableProvider>
);
return (
<>
<Tabs defaultValue='all'>
<div className='flex items-center'>
<TabsList>
<TabsTrigger value='all'>All</TabsTrigger>
<TabsTrigger value='active'>Active</TabsTrigger>
<TabsTrigger value='draft'>Draft</TabsTrigger>
<TabsTrigger value='archived' className='hidden sm:flex'>
Archived
</TabsTrigger>
</TabsList>
<div className='flex items-center gap-2 ml-auto'>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant='outline' size='sm' className='h-8 gap-1'>
<ListFilter className='h-3.5 w-3.5' />
<span className='sr-only sm:not-sr-only sm:whitespace-nowrap'>Filter</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align='end'>
<DropdownMenuLabel>Filter by</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuCheckboxItem checked>Active</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem>Draft</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem>Archived</DropdownMenuCheckboxItem>
</DropdownMenuContent>
</DropdownMenu>
<Button size='sm' variant='outline' className='h-8 gap-1'>
<File className='h-3.5 w-3.5' />
<span className='sr-only sm:not-sr-only sm:whitespace-nowrap'>Export</span>
</Button>
<Button size='sm' className='h-8 gap-1'>
<PlusCircle className='h-3.5 w-3.5' />
<span className='sr-only sm:not-sr-only sm:whitespace-nowrap'>Add Product</span>
</Button>
</div>
</div>
<TabsContent value='all'>
<Card x-chunk='dashboard-06-chunk-0'>
<CardHeader>
<CardTitle>
<Trans i18nKey='catalog.title' />
</CardTitle>
<CardDescription>
Manage your products and view their sales performance.
</CardDescription>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead className='hidden w-[100px] sm:table-cell'>
<span className='sr-only'>Image</span>
</TableHead>
<TableHead>Name</TableHead>
<TableHead>Status</TableHead>
<TableHead className='hidden md:table-cell'>Price</TableHead>
<TableHead className='hidden md:table-cell'>Total Sales</TableHead>
<TableHead className='hidden md:table-cell'>Created at</TableHead>
<TableHead>
<span className='sr-only'>Actions</span>
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell className='hidden sm:table-cell'>
<img
alt='Product image'
className='object-cover rounded-md aspect-square'
height='64'
src='/placeholder.svg'
width='64'
/>
</TableCell>
<TableCell className='font-medium'>Laser Lemonade Machine</TableCell>
<TableCell>
<Badge variant='outline'>Draft</Badge>
</TableCell>
<TableCell className='hidden md:table-cell'>$499.99</TableCell>
<TableCell className='hidden md:table-cell'>25</TableCell>
<TableCell className='hidden md:table-cell'>2023-07-12 10:42 AM</TableCell>
<TableCell>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button aria-haspopup='true' size='icon' variant='ghost'>
<MoreHorizontal className='w-4 h-4' />
<span className='sr-only'>Toggle menu</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align='end'>
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Delete</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
<TableRow>
<TableCell className='hidden sm:table-cell'>
<img
alt='Product image'
className='object-cover rounded-md aspect-square'
height='64'
src='/placeholder.svg'
width='64'
/>
</TableCell>
<TableCell className='font-medium'>Hypernova Headphones</TableCell>
<TableCell>
<Badge variant='outline'>Active</Badge>
</TableCell>
<TableCell className='hidden md:table-cell'>$129.99</TableCell>
<TableCell className='hidden md:table-cell'>100</TableCell>
<TableCell className='hidden md:table-cell'>2023-10-18 03:21 PM</TableCell>
<TableCell>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button aria-haspopup='true' size='icon' variant='ghost'>
<MoreHorizontal className='w-4 h-4' />
<span className='sr-only'>Toggle menu</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align='end'>
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Delete</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
<TableRow>
<TableCell className='hidden sm:table-cell'>
<img
alt='Product image'
className='object-cover rounded-md aspect-square'
height='64'
src='/placeholder.svg'
width='64'
/>
</TableCell>
<TableCell className='font-medium'>AeroGlow Desk Lamp</TableCell>
<TableCell>
<Badge variant='outline'>Active</Badge>
</TableCell>
<TableCell className='hidden md:table-cell'>$39.99</TableCell>
<TableCell className='hidden md:table-cell'>50</TableCell>
<TableCell className='hidden md:table-cell'>2023-11-29 08:15 AM</TableCell>
<TableCell>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button aria-haspopup='true' size='icon' variant='ghost'>
<MoreHorizontal className='w-4 h-4' />
<span className='sr-only'>Toggle menu</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align='end'>
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Delete</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
<TableRow>
<TableCell className='hidden sm:table-cell'>
<img
alt='Product image'
className='object-cover rounded-md aspect-square'
height='64'
src='/placeholder.svg'
width='64'
/>
</TableCell>
<TableCell className='font-medium'>TechTonic Energy Drink</TableCell>
<TableCell>
<Badge variant='secondary'>Draft</Badge>
</TableCell>
<TableCell className='hidden md:table-cell'>$2.99</TableCell>
<TableCell className='hidden md:table-cell'>0</TableCell>
<TableCell className='hidden md:table-cell'>2023-12-25 11:59 PM</TableCell>
<TableCell>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button aria-haspopup='true' size='icon' variant='ghost'>
<MoreHorizontal className='w-4 h-4' />
<span className='sr-only'>Toggle menu</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align='end'>
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Delete</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
<TableRow>
<TableCell className='hidden sm:table-cell'>
<img
alt='Product image'
className='object-cover rounded-md aspect-square'
height='64'
src='/placeholder.svg'
width='64'
/>
</TableCell>
<TableCell className='font-medium'>Gamer Gear Pro Controller</TableCell>
<TableCell>
<Badge variant='outline'>Active</Badge>
</TableCell>
<TableCell className='hidden md:table-cell'>$59.99</TableCell>
<TableCell className='hidden md:table-cell'>75</TableCell>
<TableCell className='hidden md:table-cell'>2024-01-01 12:00 AM</TableCell>
<TableCell>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button aria-haspopup='true' size='icon' variant='ghost'>
<MoreHorizontal className='w-4 h-4' />
<span className='sr-only'>Toggle menu</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align='end'>
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Delete</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
<TableRow>
<TableCell className='hidden sm:table-cell'>
<img
alt='Product image'
className='object-cover rounded-md aspect-square'
height='64'
src='/placeholder.svg'
width='64'
/>
</TableCell>
<TableCell className='font-medium'>Luminous VR Headset</TableCell>
<TableCell>
<Badge variant='outline'>Active</Badge>
</TableCell>
<TableCell className='hidden md:table-cell'>$199.99</TableCell>
<TableCell className='hidden md:table-cell'>30</TableCell>
<TableCell className='hidden md:table-cell'>2024-02-14 02:14 PM</TableCell>
<TableCell>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button aria-haspopup='true' size='icon' variant='ghost'>
<MoreHorizontal className='w-4 h-4' />
<span className='sr-only'>Toggle menu</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align='end'>
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Delete</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
</TableBody>
</Table>
</CardContent>
<CardFooter>
<div className='text-xs text-muted-foreground'>
Showing <strong>1-10</strong> of <strong>32</strong> products
</div>
</CardFooter>
</Card>
</TabsContent>
</Tabs>
</>
);
};

View File

@ -33,21 +33,20 @@ type SettingsDataForm = {
export const SettingsEditor = () => {
const [loading, setLoading] = useState(false);
const {
query: { data },
mutation: { mutate },
} = useSettings();
const { useQuery, useMutation } = useSettings();
console.log(data);
const { data } = useQuery;
const { mutate } = useMutation;
const form = useForm<SettingsDataForm>({
mode: "onBlur",
values: data,
defaultValues: {
contact_information: data?.contact_information ?? "",
default_payment_method: data?.default_payment_method ?? "",
default_notes: data?.default_notes ?? "",
default_legal_terms: data?.default_legal_terms ?? "",
default_quote_validity: data?.default_quote_validity ?? "",
contact_information: "",
default_payment_method: "",
default_notes: "",
default_legal_terms: "",
default_quote_validity: "",
},
/*resolver: joiResolver(
Joi.object({

View File

@ -13,7 +13,7 @@ export const useSettings = (params?: UseSettingsGetParamsType) => {
const keys = useQueryKey();
return {
query: useOne<IGetProfileResponse_DTO>({
useQuery: useOne<IGetProfileResponse_DTO>({
queryKey: keys().data().resource("settings").action("one").id("").params().get(),
queryFn: () =>
dataSource.getOne({
@ -22,7 +22,7 @@ export const useSettings = (params?: UseSettingsGetParamsType) => {
}),
...params,
}),
mutation: useSave({
useMutation: useSave({
mutationKey: keys().data().resource("settings").action("one").id("").params().get(),
mutationFn: (data) =>
dataSource.updateOne({

View File

@ -1,4 +1,4 @@
import { useCustomDialog } from "@/lib/hooks";
import { useCustomDialog, useGetIdentity } from "@/lib/hooks";
import {
Button,
DropdownMenu,
@ -32,7 +32,9 @@ export const UserButton = () => {
navigate("/logout");
},
});
//const { data, status } = useGetIdentity();
const { data, status } = useGetIdentity();
console.log(data, status);
const openUserMenu = (event: SyntheticEvent) => {
event.preventDefault();
@ -51,10 +53,18 @@ export const UserButton = () => {
return (
<DropdownMenu open={userMenuOpened} onOpenChange={setUserMenuOpened}>
<DropdownMenuTrigger asChild>
<Button variant='secondary' size='icon' className='rounded-full' onClick={openUserMenu}>
<CircleUserIcon className='w-5 h-5' />
<span className='sr-only'>{t("main_menu.user.user_menu")}</span>
</Button>
<>
{status === "success" && (
<div className='grid gap-1 text-right'>
<p className='text-sm font-medium leading-none'>{data?.name}</p>
<p className='text-sm text-muted-foreground'>{data?.email}</p>
</div>
)}
<Button variant='secondary' size='icon' className='rounded-full' onClick={openUserMenu}>
<CircleUserIcon className='w-5 h-5' />
<span className='sr-only'>{t("main_menu.user.user_menu")}</span>
</Button>
</>
</DropdownMenuTrigger>
<DropdownMenuContent align='end' className='w-56'>
<DropdownMenuLabel>{t("main_menu.user.my_account")}</DropdownMenuLabel>

View File

@ -1,4 +1,4 @@
import { useAuth } from "@/lib/hooks";
import { IdentityResponse, useAuth } from "@/lib/hooks";
import { UseQueryOptions, useQuery } from "@tanstack/react-query";
import { useQueryKey } from "../useQueryKey";
@ -6,9 +6,11 @@ export const useGetIdentity = (queryOptions?: UseQueryOptions) => {
const keys = useQueryKey();
const { getIdentity } = useAuth();
return useQuery({
return useQuery<IdentityResponse, Error>({
queryKey: keys().auth().action("identity").get(),
queryFn: getIdentity,
...queryOptions,
});
};
export const isAdmin = () => true;

View File

@ -20,10 +20,7 @@ export interface ISequelizeQueryOptions extends IRepositoryQueryOptions {
}
export interface ISequelizeQueryBuilder extends IRepositoryQueryBuilder {
generateQuery: (props: {
model: any;
queryCriteria?: IQueryCriteria;
}) => ISequelizeQueryOptions;
generateQuery: (props: { model: any; queryCriteria?: IQueryCriteria }) => ISequelizeQueryOptions;
}
export class SequelizeQueryBuilder implements ISequelizeQueryBuilder {
@ -44,15 +41,11 @@ export class SequelizeQueryBuilder implements ISequelizeQueryBuilder {
private applyQuickSearch(
model: ModelDefined<any, any>,
quickSearchCriteria: QuickSearchCriteria,
quickSearchCriteria: QuickSearchCriteria
): any {
let _model = model;
if (!quickSearchCriteria.isEmpty()) {
if (
_model &&
_model.options.scopes &&
_model.options.scopes["quickSearch"]
) {
if (_model && _model.options.scopes && _model.options.scopes["quickSearch"]) {
_model = _model.scope({
method: ["quickSearch", quickSearchCriteria.value],
});

View File

@ -30,11 +30,11 @@ export default (sequelize: Sequelize) => {
primaryKey: true,
},
contact_information: DataTypes.STRING,
default_payment_method: DataTypes.STRING,
default_notes: DataTypes.STRING,
default_legal_terms: DataTypes.STRING,
default_quote_validity: DataTypes.STRING,
contact_information: DataTypes.TEXT,
default_payment_method: DataTypes.TEXT,
default_notes: DataTypes.TEXT,
default_legal_terms: DataTypes.TEXT,
default_quote_validity: DataTypes.TEXT,
},
{
sequelize,

View File

@ -7,7 +7,6 @@ import {
Result,
UniqueID,
} from "@shared/contexts";
import { DealerRole } from "./DealerRole";
import { DealerStatus } from "./DealerStatus";
export interface IDealerProps {
@ -15,7 +14,6 @@ export interface IDealerProps {
name: Name;
logo: string;
language: Language;
roles: DealerRole[];
additionalInfo: KeyValueMap;
status: DealerStatus;
}
@ -28,7 +26,6 @@ export interface IDealer {
additionalInfo: KeyValueMap;
status: DealerStatus;
getRoles: () => DealerRole[];
}
export class Dealer extends AggregateRoot<IDealerProps> implements IDealer {
@ -37,14 +34,6 @@ export class Dealer extends AggregateRoot<IDealerProps> implements IDealer {
return Result.ok<Dealer>(user);
}
private roles: DealerRole[];
constructor(props: IDealerProps, id?: UniqueID) {
const { roles } = props;
super(props, id);
this.roles = roles;
}
get user_id(): UniqueID {
return this.props.user_id;
}
@ -64,20 +53,4 @@ export class Dealer extends AggregateRoot<IDealerProps> implements IDealer {
get additionalInfo(): KeyValueMap {
return this.props.additionalInfo;
}
get isUser(): boolean {
return this._hasRole(DealerRole.ROLE_USER);
}
get isAdmin(): boolean {
return this._hasRole(DealerRole.ROLE_ADMIN);
}
public getRoles(): DealerRole[] {
return this.roles;
}
private _hasRole(role: DealerRole): boolean {
return (this.roles || []).some((r) => r.equals(role));
}
}

View File

@ -63,13 +63,9 @@ export class DealerRepository extends SequelizeRepository<Dealer> implements IDe
}
public async findAll(queryCriteria?: IQueryCriteria): Promise<ICollection<any>> {
const { rows, count } = await this._findAll(
"Dealer_Model",
queryCriteria
/*{
include: [], // esto es para quitar las asociaciones al hacer la consulta
}*/
);
const { rows, count } = await this._findAll("Dealer_Model", queryCriteria, {
include: [], // esto es para quitar las asociaciones al hacer la consulta
});
return this.mapper.mapArrayAndCountToDomain(rows, count);
}

View File

@ -5,7 +5,6 @@ import {
} from "@/contexts/common/infrastructure";
import { KeyValueMap, Language, Name, UniqueID } from "@shared/contexts";
import { Dealer, DealerStatus, IDealerProps } from "../../domain/entities";
import { DealerRole } from "../../domain/entities/DealerRole";
import { ISalesContext } from "../Sales.context";
import { DealerCreationAttributes, Dealer_Model } from "../sequelize";
@ -26,7 +25,6 @@ class DealerMapper
logo: "",
name: this.mapsValue(source, "name", Name.create),
status: this.mapsValue(source, "status", DealerStatus.create),
roles: this.mapsValue(source, "roles", DealerRole.create),
language: this.mapsValue(source, "language", Language.createFromCode),
additionalInfo: KeyValueMap.create([
["contact_information", source.contact_information],

View File

@ -74,11 +74,11 @@ export default (sequelize: Sequelize) => {
allowNull: false,
},
contact_information: DataTypes.STRING,
default_payment_method: DataTypes.STRING,
default_notes: DataTypes.STRING,
default_legal_terms: DataTypes.STRING,
default_quote_validity: DataTypes.STRING,
contact_information: DataTypes.TEXT,
default_payment_method: DataTypes.TEXT,
default_notes: DataTypes.TEXT,
default_legal_terms: DataTypes.TEXT,
default_quote_validity: DataTypes.TEXT,
language: DataTypes.STRING,
status: {
@ -104,6 +104,13 @@ export default (sequelize: Sequelize) => {
],
whereMergeStrategy: "and", // <- cómo tratar el merge de un scope
defaultScope: {
attributes: {
exclude: ["contact_id"],
},
},
scopes: {
quickSearch(value) {
return {

View File

@ -7,13 +7,17 @@ import {
NonAttribute,
Sequelize,
} from "sequelize";
import { Dealer_Model } from "./dealer.model";
import { QuoteItem_Model } from "./quoteItem.model";
export type QuoteCreationAttributes = InferCreationAttributes<Quote_Model, { omit: "items" }>;
export type QuoteCreationAttributes = InferCreationAttributes<
Quote_Model,
{ omit: "items" | "dealer" }
>;
export class Quote_Model extends Model<
InferAttributes<Quote_Model, { omit: "items" }>,
InferCreationAttributes<Quote_Model, { omit: "items" }>
InferAttributes<Quote_Model, { omit: "items" | "dealer" }>,
InferCreationAttributes<Quote_Model, { omit: "items" | "dealer" }>
> {
static associate(connection: Sequelize) {
const { Quote_Model, QuoteItem_Model, Dealer_Model } = connection.models;
@ -39,7 +43,8 @@ export class Quote_Model extends Model<
declare subtotal: number;
declare total: number;
declare items: NonAttribute<QuoteItem_Model[]>;
declare items?: NonAttribute<QuoteItem_Model[]>;
declare dealer?: NonAttribute<Dealer_Model>;
}
export default (sequelize: Sequelize) => {